├── .gitignore ├── COPYING ├── Makefile ├── NEWS ├── README.md ├── TODO ├── debian ├── changelog ├── compat ├── control ├── copyright ├── docs ├── rules ├── source │ └── format ├── zfswatcher.init ├── zfswatcher.logrotate ├── zfswatcher.postinst ├── zfswatcher.postrm ├── zfswatcher.preinst └── zfswatcher.prerm ├── doc └── zfswatcher.8 ├── etc ├── debian-startup.sh ├── gccgo-build.sh ├── logrotate.conf ├── redhat-startup.sh ├── upstart-startup.conf ├── zfswatcher.conf └── zfswatcher.service ├── golibs └── src │ ├── code.google.com │ └── p │ │ └── gcfg │ │ ├── LICENSE │ │ ├── bool.go │ │ ├── example_test.go │ │ ├── gcfg.go │ │ ├── issues_test.go │ │ ├── read.go │ │ ├── read_test.go │ │ ├── scanenum.go │ │ ├── scanner │ │ ├── errors.go │ │ ├── example_test.go │ │ ├── scanner.go │ │ └── scanner_test.go │ │ ├── set.go │ │ ├── testdata │ │ └── gcfg_test.gcfg │ │ └── token │ │ ├── position.go │ │ ├── position_test.go │ │ ├── serialize.go │ │ ├── serialize_test.go │ │ └── token.go │ └── github.com │ ├── abbot │ └── go-http-auth │ │ ├── .gitignore │ │ ├── LICENSE │ │ ├── Makefile │ │ ├── README.md │ │ ├── auth.go │ │ ├── basic.go │ │ ├── basic_test.go │ │ ├── digest.go │ │ ├── digest_test.go │ │ ├── examples │ │ ├── basic.go │ │ ├── digest.go │ │ └── wrapped.go │ │ ├── md5crypt.go │ │ ├── md5crypt_test.go │ │ ├── misc.go │ │ ├── misc_test.go │ │ ├── test.htdigest │ │ ├── test.htpasswd │ │ ├── users.go │ │ └── users_test.go │ ├── damicon │ └── zfswatcher │ ├── ogier │ └── pflag │ │ ├── LICENSE │ │ ├── README.md │ │ ├── example_test.go │ │ ├── export_test.go │ │ ├── flag.go │ │ └── flag_test.go │ └── snabb │ └── smtp │ ├── README.md │ ├── auth.go │ └── smtp.go ├── leds.go ├── notifier ├── facility.go ├── logger_callback.go ├── logger_file.go ├── logger_smtp.go ├── logger_stdout.go ├── logger_syslog.go ├── msg.go ├── notifier.go └── severity.go ├── osutil_freebsd.go ├── osutil_linux.go ├── osutil_solaris.go ├── setup.go ├── test ├── zfs-list-details.txt ├── zfs-list.txt ├── zpool-iostat.txt ├── zpool-status-2pools.txt └── zpool-status-degraded.txt ├── util.go ├── version.go ├── webpagehandlers.go ├── webserver.go ├── www ├── resources │ ├── bootstrap │ │ ├── LICENSE │ │ ├── css │ │ │ ├── bootstrap-responsive.css │ │ │ ├── bootstrap-responsive.min.css │ │ │ ├── bootstrap.css │ │ │ └── bootstrap.min.css │ │ └── js │ │ │ ├── bootstrap-affix.js │ │ │ ├── bootstrap-alert.js │ │ │ ├── bootstrap-button.js │ │ │ ├── bootstrap-carousel.js │ │ │ ├── bootstrap-collapse.js │ │ │ ├── bootstrap-dropdown.js │ │ │ ├── bootstrap-modal.js │ │ │ ├── bootstrap-popover.js │ │ │ ├── bootstrap-scrollspy.js │ │ │ ├── bootstrap-tab.js │ │ │ ├── bootstrap-tooltip.js │ │ │ ├── bootstrap-transition.js │ │ │ ├── bootstrap-typeahead.js │ │ │ ├── bootstrap.js │ │ │ ├── bootstrap.min.js │ │ │ └── jquery.js │ ├── favicon.ico │ └── zfswatcher.css └── templates │ ├── about.html │ ├── dashboard.html │ ├── footer.html │ ├── header.html │ ├── logs.html │ ├── statistics.html │ ├── status-many.html │ ├── status-none.html │ ├── status-pool.html │ ├── status-single.html │ └── usage.html ├── zfswatcher.go ├── zfswatcher.spec └── zparse.go /.gitignore: -------------------------------------------------------------------------------- 1 | zfswatcher 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Makefile - ZFS pool monitoring and notification daemon 3 | # 4 | # Copyright © 2012-2013 Damicon Kraa Oy 5 | # 6 | # This file is part of zfswatcher. 7 | # 8 | # Zfswatcher is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # Zfswatcher is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with zfswatcher. If not, see . 20 | # 21 | 22 | # Shell: 23 | SHELL = /bin/sh 24 | 25 | # Go tool and library path: 26 | GO = /usr/local/go/bin/go 27 | GOPATH = `pwd`/golibs 28 | 29 | # Installation directories: 30 | prefix = /usr 31 | exec_prefix = $(prefix) 32 | bindir = $(exec_prefix)/bin 33 | sbindir = $(exec_prefix)/sbin 34 | datarootdir = $(prefix)/share 35 | datadir = $(datarootdir) 36 | sysconfdir = /etc 37 | docdir = $(datarootdir)/doc/zfswatcher 38 | mandir = $(datarootdir)/man 39 | man1dir = $(mandir)/man1 40 | man5dir = $(mandir)/man5 41 | man8dir = $(mandir)/man8 42 | 43 | VERSION = `fgrep VERSION version.go | cut -d\" -f2` 44 | 45 | # Rules: 46 | all: zfswatcher 47 | 48 | zfswatcher: zfswatcher.go leds.go setup.go util.go version.go webserver.go \ 49 | webpagehandlers.go zparse.go \ 50 | osutil_linux.go osutil_freebsd.go osutil_solaris.go 51 | GOPATH=$(GOPATH) $(GO) build -o $@ 52 | 53 | clean: 54 | GOPATH=$(GOPATH) $(GO) clean 55 | version=$(VERSION) && \ 56 | rm -f zfswatcher \ 57 | zfswatcher-$${version}.tar.gz \ 58 | zfswatcher-$${version}-*.rpm \ 59 | zfswatcher_$${version}-*.deb \ 60 | zfswatcher_$${version}-*.changes 61 | 62 | install: zfswatcher 63 | install -d $(DESTDIR)$(sbindir) $(DESTDIR)$(sysconfdir)/zfs \ 64 | $(DESTDIR)$(datadir)/zfswatcher \ 65 | $(DESTDIR)$(man8dir) 66 | install -c zfswatcher $(DESTDIR)$(sbindir)/zfswatcher 67 | test -e $(DESTDIR)$(sysconfdir)/zfs/zfswatcher.conf \ 68 | || install -c -m 644 etc/zfswatcher.conf \ 69 | $(DESTDIR)$(sysconfdir)/zfs/zfswatcher.conf 70 | install -c -m 644 doc/zfswatcher.8 \ 71 | $(DESTDIR)$(man8dir)/zfswatcher.8 72 | cp -R www $(DESTDIR)$(datadir)/zfswatcher/ 73 | 74 | # Make tarball: 75 | dist: 76 | version=$(VERSION) && \ 77 | git archive --prefix=zfswatcher-$${version}/ \ 78 | -o zfswatcher-$${version}.tar.gz $${version} 79 | 80 | # Make a new Debian package version: 81 | newdebversion: 82 | version=$(VERSION) && \ 83 | dch --newversion $${version}-1 \ 84 | --upstream \ 85 | --distribution unstable \ 86 | "New version $${version}" 87 | 88 | # Make Debian package: 89 | deb: 90 | version=$(VERSION) && \ 91 | dpkg-buildpackage -b -uc -tc && \ 92 | mv ../zfswatcher_$${version}-*.deb \ 93 | ../zfswatcher_$${version}-*.changes \ 94 | . 95 | 96 | # Make RPM package: 97 | rpm: dist 98 | version=$(VERSION) && \ 99 | rpmbuild=`mktemp -d "/tmp/zfswatcher-rpmbuild-XXXXXXXX"`; \ 100 | mkdir -p $$rpmbuild/TMP && \ 101 | mkdir -p $$rpmbuild/BUILD && \ 102 | mkdir -p $$rpmbuild/RPMS && \ 103 | mkdir -p $$rpmbuild/SRPMS && \ 104 | mkdir -p $$rpmbuild/SPECS && \ 105 | cp zfswatcher.spec $$rpmbuild/SPECS/ && \ 106 | mkdir -p $$rpmbuild/SOURCES && \ 107 | cp zfswatcher-$${version}.tar.gz $$rpmbuild/SOURCES/ && \ 108 | rpmbuild -ba \ 109 | --define "_topdir $$rpmbuild" \ 110 | --define "_tmppath $$rpmbuild/TMP" \ 111 | --define "version $${version}" \ 112 | zfswatcher.spec && \ 113 | cp $$rpmbuild/RPMS/*/* . && \ 114 | cp $$rpmbuild/SRPMS/* . && \ 115 | rm -r $$rpmbuild && \ 116 | echo "RPM build finished" 117 | 118 | # eof 119 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | zfswatcher - ZFS pool monitoring and notification daemon 2 | 3 | version history 4 | 5 | 6 | version 0.3 released 2013-02-20 7 | 8 | - Disk space usage warnings at configurable thresholds. 9 | 10 | - Experimental FreeBSD and OpenIndiana support. 11 | 12 | - Reconfigure on SIGHUP. 13 | 14 | - Ubuntu ZFS PPA dependency fix. 15 | 16 | - Other minor improvements. 17 | 18 | 19 | version 0.2 released 2013-02-04 20 | 21 | - Compatibility fix for changes in ZoL 0.6.0-rc14 output formatting. 22 | 23 | - Other minor improvements. 24 | 25 | 26 | version 0.01 released 2013-02-03 27 | 28 | - Initial release. 29 | 30 | /* eof */ 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | zfswatcher 2 | ========== 3 | 4 | Unmaintained 5 | ------------ 6 | 7 | NOTE: This project is unmaintained. There is a more active fork available 8 | at the following URL: https://github.com/rouben/zfswatcher 9 | 10 | *** 11 | 12 | ZFS pool monitoring and notification daemon. 13 | 14 | Please see the project web site for general information about features, 15 | supported operating environments and to download a tarball or packaged 16 | version of this software: 17 | 18 | http://zfswatcher.damicon.fi/ 19 | 20 | Source repository: 21 | 22 | https://github.com/damicon/zfswatcher/ 23 | 24 | 25 | Installing and upgrading on Debian/Ubuntu 26 | ----------------------------------------- 27 | 28 | Download the .deb package and install it with `dpkg`, for example: 29 | 30 | dpkg -i zfswatcher_0.03-1_amd64.deb 31 | 32 | The service is started by default according to the Debian and Ubuntu 33 | conventions. 34 | 35 | 36 | Installing and upgrading on RHEL/CentOS/Scientific Linux 37 | -------------------------------------------------------- 38 | 39 | Download the .rpm package and install it with `yum`, for example: 40 | 41 | yum install zfswatcher-0.03-1.x86_64.rpm 42 | 43 | The service is **not** started by default according to Red Hat 44 | conventions. It can be started as follows: 45 | 46 | service zfswatcher start 47 | 48 | 49 | Installing from source on Linux and FreeBSD 50 | ------------------------------------------- 51 | 52 | Generally it is best to use the ready made packages on Debian/Ubuntu 53 | and RHEL/CentOS/Scientific Linux. 54 | 55 | If you are packaging this software yourself or you want to compile 56 | from source for other reasons, you can follow these instructions. 57 | 58 | 59 | ### Prerequisites 60 | 61 | This software is implemented in Go programming language. Further 62 | information about installing the Go environment is available 63 | at the following URL: 64 | 65 | http://golang.org/doc/install 66 | 67 | The version 1.0.3 of the Go programming language on a 64 bit platform 68 | is recommended. 69 | 70 | The software has been developed on Debian 6.0 (squeeze) and Ubuntu 12.04 71 | (precise) but it should work fine on other Linux distributions and also 72 | recent FreeBSD versions. 73 | 74 | 75 | ### Compiling 76 | 77 | Optionally edit the Makefile to set the installation directories. 78 | Then run: 79 | 80 | make 81 | 82 | 83 | ### Installation 84 | 85 | make install 86 | 87 | 88 | ### Running 89 | 90 | There are some OS/distribution specific startup scripts in "etc" 91 | subdirectory. They may be useful. 92 | 93 | See the installed zfswatcher(8) manual page for information on invoking 94 | the zfswatcher process. 95 | 96 | 97 | Installing from source on Solaris/OpenSolaris/OpenIndiana 98 | --------------------------------------------------------- 99 | 100 | The normal "gc" Go toolchain is not available on this plaform. 101 | You need to compile a recent version of 102 | [gccgo](http://golang.org/doc/install/gccgo) from the Subversion 103 | repository at `svn://gcc.gnu.org/svn/gcc/branches/gccgo`. After that 104 | you can utilize the `etc/gccgo-build.sh` shell script. 105 | 106 | Good luck! 107 | 108 | 109 | Configuration 110 | ------------- 111 | 112 | Edit the configuration file: 113 | 114 | editor /etc/zfs/zfswatcher.conf 115 | 116 | Verify the configuration syntax: 117 | 118 | zfswatcher -t 119 | 120 | Restart the process: 121 | 122 | service zfswatcher restart 123 | 124 | Some notes: 125 | 126 | - See the configuration file comments for information about the configuration 127 | settings. 128 | 129 | - Enclosure LED management is disabled by default. Currently an external 130 | utility `ledctl` (part of ledmon package) is required for this 131 | functionality. 132 | 133 | - Logging to file `/var/log/zfswatcher.log` and local syslog daemon is enabled 134 | by default. 135 | 136 | - E-mail notifications are disabled by default. 137 | 138 | - The embedded web server is disabled by default. 139 | 140 | - If you change the default web interface templates, it is best to copy them 141 | from `/usr/share/zfswatcher/www` to another location and change the 142 | `templatedir` and `resourcedir` settings in the configuration accordingly. 143 | This way your local changes will not be overwritten if you upgrade the 144 | package at a later time. 145 | 146 | 147 | Support 148 | ------- 149 | 150 | If you encounter a bug, please open an issue at the GitHub issue 151 | tracker at: 152 | 153 | https://github.com/damicon/zfswatcher/issues 154 | 155 | Reported bugs are fixed on best effort basis. 156 | 157 | Commercial support, custom features, custom licensing and complete 158 | storage solutions are available from Damicon Kraa Oy. Contact 159 | . 160 | 161 | 162 | Contributions 163 | ------------- 164 | 165 | Contributions and suggestions are welcome. Feel free to open an issue 166 | or submit a merge request on GitHub. 167 | 168 | 169 | Authors 170 | ------- 171 | 172 | Janne Snabb 173 | 174 | 175 | License 176 | ------- 177 | 178 | Copyright © 2012-2013 Damicon Kraa Oy 179 | 180 | Zfswatcher is free software: you can redistribute it and/or modify 181 | it under the terms of the GNU General Public License as published by 182 | the Free Software Foundation, either version 3 of the License, or 183 | (at your option) any later version. 184 | 185 | Zfswatcher is distributed in the hope that it will be useful, 186 | but WITHOUT ANY WARRANTY; without even the implied warranty of 187 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 188 | GNU General Public License for more details. 189 | 190 | You should have received a copy of the GNU General Public License 191 | along with zfswatcher. If not, see . 192 | 193 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | - configurable attachment 2 | - udev trigger led update? 3 | - configurable device read/write/cksum error thresholds 4 | - persist state 5 | - web interface: remove unused javascripts 6 | - web interface: statistics 7 | - web interface: user access levels? 8 | - internal led control (replace ledctl) 9 | - notification output to programs (for example to send snmp traps) 10 | - hot spare replace, automagic? 11 | https://github.com/zfsonlinux/zfs/issues/250 workaround: remove, replace 12 | - configurable severity for parse errors etc. 13 | - function for sending test notification at desired level 14 | - lots of cleanup & refactoring :) 15 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | zfswatcher (0.03-2) unstable; urgency=low 2 | 3 | * Added "debian-zfs" as an alternative dependency. 4 | 5 | -- Janne Snabb Wed, 07 Aug 2013 21:23:37 +0300 6 | 7 | zfswatcher (0.03-1) unstable; urgency=low 8 | 9 | * New version 0.03 10 | 11 | -- Janne Snabb Wed, 20 Feb 2013 19:32:46 +0200 12 | 13 | zfswatcher (0.02-1) unstable; urgency=low 14 | 15 | * New version 0.02 16 | 17 | -- Janne Snabb Mon, 04 Feb 2013 22:15:03 +0200 18 | 19 | zfswatcher (0.01-1) unstable; urgency=low 20 | 21 | * Initial release. 22 | 23 | -- Janne Snabb Wed, 30 Jan 2013 23:31:24 +0200 24 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 8 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: zfswatcher 2 | Section: utils 3 | Priority: extra 4 | Maintainer: Janne Snabb 5 | Build-Depends: debhelper (>= 8.0.0) 6 | Standards-Version: 3.9.3 7 | Homepage: http://zfswatcher.damicon.fi/ 8 | 9 | Package: zfswatcher 10 | Architecture: amd64 11 | Depends: zfs | ubuntu-zfs | debian-zfs, ${shlibs:Depends}, ${misc:Depends} 12 | Recommends: ledmon 13 | Description: ZFS pool monitoring and notification daemon 14 | Zfswatcher is ZFS pool monitoring and notification daemon 15 | with the following main features: 16 | * Periodically inspects the zpool status. 17 | * Sends configurable notifications on status changes. 18 | * Controls the disk enclosure LEDs. 19 | * Web interface for displaying status and logs. 20 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: zfswatcher 3 | Source: http://zfswatcher.damicon.fi/ 4 | 5 | Files: * 6 | Copyright: 2012, 2013 Damicon Kraa Oy 7 | License: GPL-3.0+ 8 | Zfswatcher is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation, either version 3 of the License, or 11 | (at your option) any later version. 12 | . 13 | Zfswatcher is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | . 18 | You should have received a copy of the GNU General Public License 19 | along with zfswatcher. If not, see . 20 | . 21 | On Debian systems, the complete text of the GNU General 22 | Public License version 3 can be found in "/usr/share/common-licenses/GPL-3". 23 | 24 | Files: golibs/src/code.google.com/p/gcfg/* 25 | Copyright: 2012 Péter Surányi 26 | 2009 The Go Authors 27 | License: BSD-2-clause and BSD-3-clause 28 | 29 | Files: golibs/src/github.com/abbot/go-http-auth/* 30 | Copyright: 2012-2013 Lev Shamardin 31 | License: Apache-2.0 32 | 33 | Files: golibs/src/github.com/ogier/pflag/* 34 | Copyright: 2012 Alex Ogier 35 | 2012 The Go Authors 36 | License: BSD-2-clause 37 | 38 | Files: www/resources/bootstrap/js/* www/resources/bootstrap/css/* 39 | Copyright: 2012 Twitter, Inc 40 | License: Apache-2.0 41 | 42 | Files: www/resources/bootstrap/js/jquery.js 43 | Copyright: 2012 jQuery Foundation and other contributors 44 | License: Expat 45 | -------------------------------------------------------------------------------- /debian/docs: -------------------------------------------------------------------------------- 1 | README.md 2 | NEWS 3 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # -*- makefile -*- 3 | # Sample debian/rules that uses debhelper. 4 | # This file was originally written by Joey Hess and Craig Small. 5 | # As a special exception, when this file is copied by dh-make into a 6 | # dh-make output file, you may use that output file without restriction. 7 | # This special exception was added by Craig Small in version 0.37 of dh-make. 8 | 9 | # Uncomment this to turn on verbose mode. 10 | #export DH_VERBOSE=1 11 | 12 | %: 13 | dh $@ 14 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (quilt) 2 | -------------------------------------------------------------------------------- /debian/zfswatcher.init: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | ### BEGIN INIT INFO 3 | # Provides: zfswatcher 4 | # Required-Start: udev $syslog $network $remote_fs 5 | # Required-Stop: udev $syslog $network $remote_fs 6 | # Default-Start: 2 3 4 5 7 | # Default-Stop: 0 1 6 8 | # Short-Description: ZFS monitoring daemon 9 | # Description: ZFS monitoring daemon 10 | ### END INIT INFO 11 | 12 | # Do NOT "set -e" 13 | 14 | # PATH should only include /usr/* if it runs after the mountnfs.sh script 15 | PATH=/sbin:/usr/sbin:/bin:/usr/bin 16 | DESC="ZFS monitoring daemon" 17 | NAME=zfswatcher 18 | DAEMON=/usr/sbin/$NAME 19 | DAEMON_ARGS="" 20 | PIDFILE=/var/run/$NAME.pid 21 | SCRIPTNAME=/etc/init.d/$NAME 22 | 23 | # Exit if the package is not installed 24 | [ -x "$DAEMON" ] || exit 0 25 | 26 | # Read configuration variable file if it is present 27 | [ -r /etc/default/$NAME ] && . /etc/default/$NAME 28 | 29 | # Load the VERBOSE setting and other rcS variables 30 | . /lib/init/vars.sh 31 | 32 | # Define LSB log_* functions. 33 | # Depend on lsb-base (>= 3.2-14) to ensure that this file is present 34 | # and status_of_proc is working. 35 | . /lib/lsb/init-functions 36 | 37 | # 38 | # Function that starts the daemon/service 39 | # 40 | do_start() 41 | { 42 | # Return 43 | # 0 if daemon has been started 44 | # 1 if daemon was already running 45 | # 2 if daemon could not be started 46 | start-stop-daemon --start --quiet --background --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \ 47 | || return 1 48 | start-stop-daemon --start --quiet --background --pidfile $PIDFILE --exec $DAEMON -- \ 49 | $DAEMON_ARGS \ 50 | || return 2 51 | # Add code here, if necessary, that waits for the process to be ready 52 | # to handle requests from services started subsequently which depend 53 | # on this one. As a last resort, sleep for some time. 54 | } 55 | 56 | # 57 | # Function that stops the daemon/service 58 | # 59 | do_stop() 60 | { 61 | # Return 62 | # 0 if daemon has been stopped 63 | # 1 if daemon was already stopped 64 | # 2 if daemon could not be stopped 65 | # other if a failure occurred 66 | start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME 67 | RETVAL="$?" 68 | [ "$RETVAL" = 2 ] && return 2 69 | # Wait for children to finish too if this is a daemon that forks 70 | # and if the daemon is only ever run from this initscript. 71 | # If the above conditions are not satisfied then add some other code 72 | # that waits for the process to drop all resources that could be 73 | # needed by services started subsequently. A last resort is to 74 | # sleep for some time. 75 | start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON 76 | [ "$?" = 2 ] && return 2 77 | # Many daemons don't delete their pidfiles when they exit. 78 | rm -f $PIDFILE 79 | return "$RETVAL" 80 | } 81 | 82 | # 83 | # Function that sends a SIGHUP to the daemon/service 84 | # 85 | do_reload() { 86 | # 87 | # If the daemon can reload its configuration without 88 | # restarting (for example, when it is sent a SIGHUP), 89 | # then implement that here. 90 | # 91 | start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME 92 | return 0 93 | } 94 | 95 | case "$1" in 96 | start) 97 | [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" 98 | do_start 99 | case "$?" in 100 | 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 101 | 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; 102 | esac 103 | ;; 104 | stop) 105 | [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" 106 | do_stop 107 | case "$?" in 108 | 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 109 | 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; 110 | esac 111 | ;; 112 | status) 113 | status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $? 114 | ;; 115 | #reload|force-reload) 116 | # 117 | # If do_reload() is not implemented then leave this commented out 118 | # and leave 'force-reload' as an alias for 'restart'. 119 | # 120 | #log_daemon_msg "Reloading $DESC" "$NAME" 121 | #do_reload 122 | #log_end_msg $? 123 | #;; 124 | restart|force-reload) 125 | # 126 | # If the "reload" option is implemented then remove the 127 | # 'force-reload' alias 128 | # 129 | log_daemon_msg "Restarting $DESC" "$NAME" 130 | do_stop 131 | case "$?" in 132 | 0|1) 133 | do_start 134 | case "$?" in 135 | 0) log_end_msg 0 ;; 136 | 1) log_end_msg 1 ;; # Old process is still running 137 | *) log_end_msg 1 ;; # Failed to start 138 | esac 139 | ;; 140 | *) 141 | # Failed to stop 142 | log_end_msg 1 143 | ;; 144 | esac 145 | ;; 146 | *) 147 | #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2 148 | echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2 149 | exit 3 150 | ;; 151 | esac 152 | 153 | : 154 | -------------------------------------------------------------------------------- /debian/zfswatcher.logrotate: -------------------------------------------------------------------------------- 1 | /var/lib/zfswatcher.log 2 | { 3 | rotate 6 4 | monthly 5 | missingok 6 | notifempty 7 | compress 8 | delaycompress 9 | postrotate 10 | start-stop-daemon -K -p /var/run/zfswatcher.pid -s HUP -x /usr/sbin/zfswatcher -q 11 | endscript 12 | } 13 | -------------------------------------------------------------------------------- /debian/zfswatcher.postinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # postinst script for zfswatcher 3 | # 4 | # see: dh_installdeb(1) 5 | 6 | set -e 7 | 8 | # summary of how this script can be called: 9 | # * `configure' 10 | # * `abort-upgrade' 11 | # * `abort-remove' `in-favour' 12 | # 13 | # * `abort-remove' 14 | # * `abort-deconfigure' `in-favour' 15 | # `removing' 16 | # 17 | # for details, see http://www.debian.org/doc/debian-policy/ or 18 | # the debian-policy package 19 | 20 | 21 | case "$1" in 22 | configure) 23 | ;; 24 | 25 | abort-upgrade|abort-remove|abort-deconfigure) 26 | ;; 27 | 28 | *) 29 | echo "postinst called with unknown argument \`$1'" >&2 30 | exit 1 31 | ;; 32 | esac 33 | 34 | # dh_installdeb will replace this with shell code automatically 35 | # generated by other debhelper scripts. 36 | 37 | #DEBHELPER# 38 | 39 | exit 0 40 | -------------------------------------------------------------------------------- /debian/zfswatcher.postrm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # postrm script for zfswatcher 3 | # 4 | # see: dh_installdeb(1) 5 | 6 | set -e 7 | 8 | # summary of how this script can be called: 9 | # * `remove' 10 | # * `purge' 11 | # * `upgrade' 12 | # * `failed-upgrade' 13 | # * `abort-install' 14 | # * `abort-install' 15 | # * `abort-upgrade' 16 | # * `disappear' 17 | # 18 | # for details, see http://www.debian.org/doc/debian-policy/ or 19 | # the debian-policy package 20 | 21 | 22 | case "$1" in 23 | purge|remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) 24 | ;; 25 | 26 | *) 27 | echo "postrm called with unknown argument \`$1'" >&2 28 | exit 1 29 | ;; 30 | esac 31 | 32 | if [ "$1" = "purge" ] ; then 33 | rm -f /var/log/zfswatcher.log* 34 | rm -f /var/run/zfswatcher.pid 35 | fi 36 | 37 | # dh_installdeb will replace this with shell code automatically 38 | # generated by other debhelper scripts. 39 | 40 | #DEBHELPER# 41 | 42 | exit 0 43 | -------------------------------------------------------------------------------- /debian/zfswatcher.preinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # preinst script for zfswatcher 3 | # 4 | # see: dh_installdeb(1) 5 | 6 | set -e 7 | 8 | # summary of how this script can be called: 9 | # * `install' 10 | # * `install' 11 | # * `upgrade' 12 | # * `abort-upgrade' 13 | # for details, see http://www.debian.org/doc/debian-policy/ or 14 | # the debian-policy package 15 | 16 | 17 | case "$1" in 18 | install|upgrade) 19 | ;; 20 | 21 | abort-upgrade) 22 | ;; 23 | 24 | *) 25 | echo "preinst called with unknown argument \`$1'" >&2 26 | exit 1 27 | ;; 28 | esac 29 | 30 | # dh_installdeb will replace this with shell code automatically 31 | # generated by other debhelper scripts. 32 | 33 | #DEBHELPER# 34 | 35 | exit 0 36 | -------------------------------------------------------------------------------- /debian/zfswatcher.prerm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # prerm script for zfswatcher 3 | # 4 | # see: dh_installdeb(1) 5 | 6 | set -e 7 | 8 | # summary of how this script can be called: 9 | # * `remove' 10 | # * `upgrade' 11 | # * `failed-upgrade' 12 | # * `remove' `in-favour' 13 | # * `deconfigure' `in-favour' 14 | # `removing' 15 | # 16 | # for details, see http://www.debian.org/doc/debian-policy/ or 17 | # the debian-policy package 18 | 19 | 20 | case "$1" in 21 | remove|upgrade|deconfigure) 22 | ;; 23 | 24 | failed-upgrade) 25 | ;; 26 | 27 | *) 28 | echo "prerm called with unknown argument \`$1'" >&2 29 | exit 1 30 | ;; 31 | esac 32 | 33 | # dh_installdeb will replace this with shell code automatically 34 | # generated by other debhelper scripts. 35 | 36 | #DEBHELPER# 37 | 38 | exit 0 39 | -------------------------------------------------------------------------------- /doc/zfswatcher.8: -------------------------------------------------------------------------------- 1 | .\"- 2 | .\" zfswatcher.8 3 | .\" 4 | .\" Copyright © 2012-2013 Damicon Kraa Oy 5 | .\" 6 | .\" This file is part of zfswatcher. 7 | .\" 8 | .\" Zfswatcher is free software: you can redistribute it and/or modify 9 | .\" it under the terms of the GNU General Public License as published by 10 | .\" the Free Software Foundation, either version 3 of the License, or 11 | .\" (at your option) any later version. 12 | .\" 13 | .\" Zfswatcher is distributed in the hope that it will be useful, 14 | .\" but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | .\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | .\" GNU General Public License for more details. 17 | .\" 18 | .\" You should have received a copy of the GNU General Public License 19 | .\" along with zfswatcher. If not, see . 20 | .\" 21 | .TH ZFSWATCHER 8 "2013-02-18" "version 0.03" 22 | .SH NAME 23 | zfswatcher \- ZFS pool monitoring and notification daemon 24 | .SH SYNOPSIS 25 | .B zfswatcher 26 | .RB [\| \-c 27 | .IR config-file \|] 28 | .RB [\| \-d \|] 29 | .RB [\| \-P \|] 30 | .RB [\| \-t \|] 31 | .RB [\| \-v \|] 32 | .SH DESCRIPTION 33 | The 34 | .B zfswatcher 35 | daemon periodically checks the ZFS pool status and sends notifications 36 | about any changes. It also optionally controls the disk enclosure LEDs 37 | and provides a web interface for viewing the ZFS status and logs. 38 | .SH OPTIONS 39 | .TP 40 | .BR \-c ", " \-\-conf " \fIconfig-file\fR" 41 | Use an alternate 42 | .I config-file 43 | instead of the default 44 | .IR /etc/zfs/zfswatcher.conf . 45 | .TP 46 | .BR \-d ", " \-\-debug 47 | Enable debug mode which outputs all messages to STDOUT. 48 | .TP 49 | .BR \-P ", " \-\-passwordhash 50 | Read a password and print a hashed password. This can be useful when 51 | setting up the configuration files. 52 | .TP 53 | .BR \-t ", " \-\-test 54 | Test the configuration file syntax and exit. 55 | .TP 56 | .BR \-v ", " \-\-version 57 | Print version information and exit. 58 | .SH SIGNALS 59 | .TP 60 | .B SIGHUP 61 | Reload configuration and reopen logs. 62 | .TP 63 | .BR SIGTERM ", " SIGINT 64 | Terminate gracefully. 65 | .SH EXIT STATUS 66 | The daemon exits 0 on success, and >0 if an error occurs. 67 | .SH FILES 68 | .TP 69 | .B /etc/zfs/zfswatcher.conf 70 | Default configuration file. 71 | .SH NOTES 72 | The 73 | .B zfswatcher 74 | process does not "daemonize" itself (fork or double-fork to become a 75 | background process) like most traditional Unix daemons do. This is a 76 | modern approach and allows system process management daemons such as 77 | .BR upstart (8) 78 | and 79 | .BR systemd (8) 80 | to keep better control of the service. This can be worked around by 81 | using a tool such as 82 | .BR start-stop-daemon (8). 83 | .PP 84 | Currently an external utility called 85 | .B ledctl 86 | which is part of the 87 | .B ledmon 88 | package is required for enclosure LED control. 89 | .SH COPYRIGHT 90 | Copyright \(co 2012-2013 Damicon Kraa Oy 91 | .br 92 | This is free software; see the source for copying conditions. There is NO 93 | warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 94 | .SH SEE ALSO 95 | .BR zfs (8), 96 | .BR zpool (8), 97 | .ie !d pdfhref \ 98 | http://zfswatcher.damicon.fi/ 99 | .el \ 100 | .pdfhref W http://zfswatcher.damicon.fi/ 101 | .\" eof 102 | -------------------------------------------------------------------------------- /etc/debian-startup.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | ### BEGIN INIT INFO 3 | # Provides: zfswatcher 4 | # Required-Start: udev $syslog $network $remote_fs 5 | # Required-Stop: udev $syslog $network $remote_fs 6 | # Default-Start: 2 3 4 5 7 | # Default-Stop: 0 1 6 8 | # Short-Description: ZFS monitoring daemon 9 | # Description: ZFS monitoring daemon 10 | ### END INIT INFO 11 | 12 | # Do NOT "set -e" 13 | 14 | # PATH should only include /usr/* if it runs after the mountnfs.sh script 15 | PATH=/sbin:/usr/sbin:/bin:/usr/bin 16 | DESC="ZFS monitoring daemon" 17 | NAME=zfswatcher 18 | DAEMON=/usr/sbin/$NAME 19 | DAEMON_ARGS="" 20 | PIDFILE=/var/run/$NAME.pid 21 | SCRIPTNAME=/etc/init.d/$NAME 22 | 23 | # Exit if the package is not installed 24 | [ -x "$DAEMON" ] || exit 0 25 | 26 | # Read configuration variable file if it is present 27 | [ -r /etc/default/$NAME ] && . /etc/default/$NAME 28 | 29 | # Load the VERBOSE setting and other rcS variables 30 | . /lib/init/vars.sh 31 | 32 | # Define LSB log_* functions. 33 | # Depend on lsb-base (>= 3.2-14) to ensure that this file is present 34 | # and status_of_proc is working. 35 | . /lib/lsb/init-functions 36 | 37 | # 38 | # Function that starts the daemon/service 39 | # 40 | do_start() 41 | { 42 | # Return 43 | # 0 if daemon has been started 44 | # 1 if daemon was already running 45 | # 2 if daemon could not be started 46 | start-stop-daemon --start --quiet --background --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \ 47 | || return 1 48 | start-stop-daemon --start --quiet --background --pidfile $PIDFILE --exec $DAEMON -- \ 49 | $DAEMON_ARGS \ 50 | || return 2 51 | # Add code here, if necessary, that waits for the process to be ready 52 | # to handle requests from services started subsequently which depend 53 | # on this one. As a last resort, sleep for some time. 54 | } 55 | 56 | # 57 | # Function that stops the daemon/service 58 | # 59 | do_stop() 60 | { 61 | # Return 62 | # 0 if daemon has been stopped 63 | # 1 if daemon was already stopped 64 | # 2 if daemon could not be stopped 65 | # other if a failure occurred 66 | start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME 67 | RETVAL="$?" 68 | [ "$RETVAL" = 2 ] && return 2 69 | # Wait for children to finish too if this is a daemon that forks 70 | # and if the daemon is only ever run from this initscript. 71 | # If the above conditions are not satisfied then add some other code 72 | # that waits for the process to drop all resources that could be 73 | # needed by services started subsequently. A last resort is to 74 | # sleep for some time. 75 | start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON 76 | [ "$?" = 2 ] && return 2 77 | # Many daemons don't delete their pidfiles when they exit. 78 | rm -f $PIDFILE 79 | return "$RETVAL" 80 | } 81 | 82 | # 83 | # Function that sends a SIGHUP to the daemon/service 84 | # 85 | do_reload() { 86 | # 87 | # If the daemon can reload its configuration without 88 | # restarting (for example, when it is sent a SIGHUP), 89 | # then implement that here. 90 | # 91 | start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME 92 | return 0 93 | } 94 | 95 | case "$1" in 96 | start) 97 | [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" 98 | do_start 99 | case "$?" in 100 | 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 101 | 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; 102 | esac 103 | ;; 104 | stop) 105 | [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" 106 | do_stop 107 | case "$?" in 108 | 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 109 | 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; 110 | esac 111 | ;; 112 | status) 113 | status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $? 114 | ;; 115 | reload|force-reload) 116 | log_daemon_msg "Reloading $DESC" "$NAME" 117 | do_reload 118 | log_end_msg $? 119 | ;; 120 | restart) 121 | log_daemon_msg "Restarting $DESC" "$NAME" 122 | do_stop 123 | case "$?" in 124 | 0|1) 125 | do_start 126 | case "$?" in 127 | 0) log_end_msg 0 ;; 128 | 1) log_end_msg 1 ;; # Old process is still running 129 | *) log_end_msg 1 ;; # Failed to start 130 | esac 131 | ;; 132 | *) 133 | # Failed to stop 134 | log_end_msg 1 135 | ;; 136 | esac 137 | ;; 138 | *) 139 | echo "Usage: $SCRIPTNAME {start|stop|status|restart|reload|force-reload}" >&2 140 | exit 3 141 | ;; 142 | esac 143 | 144 | : 145 | -------------------------------------------------------------------------------- /etc/gccgo-build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | GCCGO=gccgo 6 | GOOS=solaris 7 | GOARCH=amd64 8 | 9 | INC=golibs/src 10 | 11 | subpackages="" 12 | 13 | compile_go_package () { 14 | module="$1" 15 | shift 16 | files="" 17 | 18 | for f in $* 19 | do 20 | files="$files $INC/$module/$f" 21 | done 22 | 23 | set -x 24 | $GCCGO -I $INC -c -o $INC/$module.o $files 25 | set +x 26 | 27 | subpackages="$subpackages $INC/$module.o" 28 | } 29 | 30 | compile_go_package code.google.com/p/gcfg/token \ 31 | token.go position.go serialize.go 32 | compile_go_package code.google.com/p/gcfg/scanner \ 33 | scanner.go errors.go 34 | compile_go_package code.google.com/p/gcfg \ 35 | gcfg.go bool.go read.go scanenum.go set.go 36 | compile_go_package github.com/abbot/go-http-auth \ 37 | auth.go basic.go digest.go md5crypt.go misc.go users.go 38 | compile_go_package github.com/ogier/pflag \ 39 | flag.go 40 | compile_go_package github.com/snabb/smtp \ 41 | smtp.go auth.go 42 | compile_go_package github.com/damicon/zfswatcher/notifier \ 43 | notifier.go facility.go severity.go msg.go logger_*.go 44 | 45 | set -x 46 | $GCCGO -I $INC -o zfswatcher \ 47 | zfswatcher.go leds.go util.go version.go webserver.go \ 48 | webpagehandlers.go setup.go zparse.go osutil_$GOOS.go \ 49 | $subpackages 50 | 51 | # eof 52 | -------------------------------------------------------------------------------- /etc/logrotate.conf: -------------------------------------------------------------------------------- 1 | /var/lib/zfswatcher.log 2 | { 3 | rotate 6 4 | monthly 5 | missingok 6 | notifempty 7 | compress 8 | delaycompress 9 | postrotate 10 | kill -HUP `cat /var/run/zfswatcher.pid` 2>/dev/null 2>&1 || true 11 | endscript 12 | } 13 | -------------------------------------------------------------------------------- /etc/redhat-startup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # zfswatcher - ZFS monitoring daemon 4 | # 5 | # chkconfig: 2345 80 20 6 | # description: ZFS monitoring daemon 7 | 8 | # http://fedoraproject.org/wiki/FCNewInit/Initscripts 9 | ### BEGIN INIT INFO 10 | # Provides: zfswatcher 11 | # Required-Start: zfs $syslog $network $remote_fs 12 | # Required-Stop: zfs $syslog $network $remote_fs 13 | # Default-Start: 2 3 4 5 14 | # Default-Stop: 0 1 6 15 | # Short-Description: ZFS monitoring daemon 16 | # Description: ZFS monitoring daemon 17 | ### END INIT INFO 18 | 19 | # Source function library. 20 | . /etc/rc.d/init.d/functions 21 | 22 | exec="/usr/sbin/zfswatcher" 23 | prog=$(basename $exec) 24 | 25 | [ -e /etc/sysconfig/$prog ] && . /etc/sysconfig/$prog 26 | 27 | lockfile=/var/lock/subsys/$prog 28 | 29 | start() { 30 | echo -n $"Starting $prog: " 31 | daemon "$exec &" 32 | retval=$? 33 | echo 34 | [ $retval -eq 0 ] && touch $lockfile 35 | return $retval 36 | } 37 | 38 | stop() { 39 | echo -n $"Stopping $prog: " 40 | killproc $prog 41 | retval=$? 42 | echo 43 | [ $retval -eq 0 ] && rm -f $lockfile 44 | return $retval 45 | } 46 | 47 | restart() { 48 | stop 49 | start 50 | } 51 | 52 | case "$1" in 53 | start|stop|restart) 54 | $1 55 | ;; 56 | force-reload) 57 | restart 58 | ;; 59 | status) 60 | status $prog 61 | ;; 62 | try-restart|condrestart) 63 | if status $prog >/dev/null ; then 64 | restart 65 | fi 66 | ;; 67 | reload) 68 | status $prog >/dev/null || exit 7 69 | killproc $prog -HUP 70 | ;; 71 | *) 72 | echo $"Usage: $0 {start|stop|status|restart|try-restart|reload|force-reload}" 73 | exit 2 74 | esac 75 | -------------------------------------------------------------------------------- /etc/upstart-startup.conf: -------------------------------------------------------------------------------- 1 | # zfswatcher - ZFS monitoring daemon 2 | # 3 | # This file should be installed as /etc/init/zfswatcher.conf 4 | # on architectures which use upstart(8) to bring up system 5 | # daemons. 6 | 7 | description "ZFS monitoring daemon" 8 | 9 | start on runlevel [2345] 10 | stop on runlevel [!2345] 11 | 12 | exec /usr/sbin/zfswatcher 13 | -------------------------------------------------------------------------------- /etc/zfswatcher.service: -------------------------------------------------------------------------------- 1 | # 2 | # zfswatcher unit configuration file for systemd(8) 3 | # 4 | 5 | [Unit] 6 | Description=ZFS monitoring daemon 7 | Documentation=man:zfswatcher(8) http://zfswatcher.damicon.fi/ 8 | 9 | [Service] 10 | Type=simple 11 | ExecStart=/usr/sbin/zfswatcher 12 | 13 | [Install] 14 | WantedBy=multi-user.target 15 | -------------------------------------------------------------------------------- /golibs/src/code.google.com/p/gcfg/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Péter Surányi. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 15 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 16 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 17 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 18 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 19 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 20 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 21 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 22 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | 26 | ---------------------------------------------------------------------- 27 | Portions of gcfg's source code have been derived from Go, and are 28 | covered by the following license: 29 | ---------------------------------------------------------------------- 30 | 31 | Copyright (c) 2009 The Go Authors. All rights reserved. 32 | 33 | Redistribution and use in source and binary forms, with or without 34 | modification, are permitted provided that the following conditions are 35 | met: 36 | 37 | * Redistributions of source code must retain the above copyright 38 | notice, this list of conditions and the following disclaimer. 39 | * Redistributions in binary form must reproduce the above 40 | copyright notice, this list of conditions and the following disclaimer 41 | in the documentation and/or other materials provided with the 42 | distribution. 43 | * Neither the name of Google Inc. nor the names of its 44 | contributors may be used to endorse or promote products derived from 45 | this software without specific prior written permission. 46 | 47 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 48 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 49 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 50 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 51 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 52 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 53 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 54 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 55 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 56 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 57 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 58 | -------------------------------------------------------------------------------- /golibs/src/code.google.com/p/gcfg/bool.go: -------------------------------------------------------------------------------- 1 | package gcfg 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type gbool bool 8 | 9 | var gboolValues = map[string]interface{}{ 10 | "true": true, "yes": true, "on": true, "1": true, 11 | "false": false, "no": false, "off": false, "0": false} 12 | 13 | func (b *gbool) Scan(state fmt.ScanState, verb rune) error { 14 | v, err := scanEnum(state, gboolValues, true) 15 | if err != nil { 16 | return err 17 | } 18 | bb, _ := v.(bool) // cannot be non-bool 19 | *b = gbool(bb) 20 | return nil 21 | } 22 | -------------------------------------------------------------------------------- /golibs/src/code.google.com/p/gcfg/example_test.go: -------------------------------------------------------------------------------- 1 | package gcfg_test 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | ) 7 | 8 | import "code.google.com/p/gcfg" 9 | 10 | func ExampleReadStringInto() { 11 | cfgStr := `; Comment line 12 | [section] 13 | name=value # comment` 14 | cfg := struct { 15 | Section struct { 16 | Name string 17 | } 18 | }{} 19 | err := gcfg.ReadStringInto(&cfg, cfgStr) 20 | if err != nil { 21 | log.Fatalf("Failed to parse gcfg data: %s", err) 22 | } 23 | fmt.Println(cfg.Section.Name) 24 | // Output: value 25 | } 26 | 27 | func ExampleReadStringInto_bool() { 28 | cfgStr := `; Comment line 29 | [section] 30 | switch=on` 31 | cfg := struct { 32 | Section struct { 33 | Switch bool 34 | } 35 | }{} 36 | err := gcfg.ReadStringInto(&cfg, cfgStr) 37 | if err != nil { 38 | log.Fatalf("Failed to parse gcfg data: %s", err) 39 | } 40 | fmt.Println(cfg.Section.Switch) 41 | // Output: true 42 | } 43 | 44 | func ExampleReadStringInto_subsections() { 45 | cfgStr := `; Comment line 46 | [profile "A"] 47 | color = white 48 | 49 | [profile "B"] 50 | color = black 51 | ` 52 | cfg := struct { 53 | Profile map[string]*struct { 54 | Color string 55 | } 56 | }{} 57 | err := gcfg.ReadStringInto(&cfg, cfgStr) 58 | if err != nil { 59 | log.Fatalf("Failed to parse gcfg data: %s", err) 60 | } 61 | fmt.Printf("%s %s\n", cfg.Profile["A"].Color, cfg.Profile["B"].Color) 62 | // Output: white black 63 | } 64 | -------------------------------------------------------------------------------- /golibs/src/code.google.com/p/gcfg/gcfg.go: -------------------------------------------------------------------------------- 1 | // Package gcfg reads "gitconfig-like" text-based configuration files with 2 | // "name=value" pairs grouped into sections (gcfg files). 3 | // Support for writing gcfg files may be added later. 4 | // 5 | // See ReadInto and the examples to get an idea of how to use it. 6 | // 7 | // This package is still a work in progress, and both the supported syntax and 8 | // the API is subject to change. See below for planned changes. 9 | // 10 | // The syntax is based on that used by git config: 11 | // http://git-scm.com/docs/git-config#_syntax . 12 | // There are some (planned) differences compared to the git config format: 13 | // - improve data portability: 14 | // - must be encoded in UTF-8 (for now) and must not contain the 0 byte 15 | // - include and "path" type is not supported 16 | // (path type may be implementable as a user-defined type) 17 | // - disallow potentially ambiguous or misleading definitions: 18 | // - `[sec.sub]` format is not allowed (deprecated in gitconfig) 19 | // - `[sec ""]` is not allowed 20 | // - use `[sec]` for section name "sec" and empty subsection name 21 | // - (planned) within a single file, definitions must be contiguous for each: 22 | // - section: '[secA]' -> '[secB]' -> '[secA]' is an error 23 | // - subsection: '[sec "A"]' -> '[sec "B"]' -> '[sec "A"]' is an error 24 | // - multivalued variable: 'multi=a' -> 'other=x' -> 'multi=b' is an error 25 | // 26 | // The package may be usable for handling some of the various "INI file" formats 27 | // used by some programs and libraries, but achieving or maintaining 28 | // compatibility with any of those is not a primary concern. 29 | // 30 | // TODO: 31 | // - format 32 | // - define valid section and variable names 33 | // - reconsider valid escape sequences 34 | // (gitconfig doesn't support \r in value, \t in subsection name, etc.) 35 | // - define handling of "implicit value" for types other than bool 36 | // - consider handling of numeric values (decimal only by default?) 37 | // - complete syntax documentation 38 | // - reading 39 | // - define internal representation structure 40 | // - support multi-value variables 41 | // - support multiple inputs (readers, strings, files) 42 | // - support declaring encoding (?) 43 | // - support automatic dereferencing of pointer fields (?) 44 | // - support varying fields sets for subsections (?) 45 | // - scanEnum 46 | // - should use longest match (?) 47 | // - support matching on unique prefix (?) 48 | // - writing gcfg files 49 | // - error handling 50 | // - report position of extra characters in value 51 | // - make error context accessible programmatically? 52 | // - limit input size? 53 | // - move TODOs to issue tracker (eventually) 54 | // 55 | package gcfg 56 | -------------------------------------------------------------------------------- /golibs/src/code.google.com/p/gcfg/issues_test.go: -------------------------------------------------------------------------------- 1 | package gcfg 2 | 3 | import ( 4 | "fmt" 5 | "math/big" 6 | "strings" 7 | "testing" 8 | ) 9 | 10 | type Config1 struct { 11 | Section struct { 12 | Int int 13 | BigInt big.Int 14 | } 15 | } 16 | 17 | var testsIssue1 = []struct { 18 | cfg string 19 | typename string 20 | }{ 21 | {"[section]\nint=X", "int"}, 22 | {"[section]\nint=", "int"}, 23 | {"[section]\nint=1A", "int"}, 24 | {"[section]\nbigint=X", "big.Int"}, 25 | {"[section]\nbigint=", "big.Int"}, 26 | {"[section]\nbigint=1A", "big.Int"}, 27 | } 28 | 29 | // Value parse error should: 30 | // - include plain type name 31 | // - not include reflect internals 32 | func TestIssue1(t *testing.T) { 33 | for i, tt := range testsIssue1 { 34 | var c Config1 35 | err := ReadStringInto(&c, tt.cfg) 36 | switch { 37 | case err == nil: 38 | t.Errorf("%d fail: got ok; wanted error", i) 39 | case !strings.Contains(err.Error(), tt.typename): 40 | t.Errorf("%d fail: error message doesn't contain type name %q: %v", 41 | i, tt.typename, err) 42 | case strings.Contains(err.Error(), "reflect"): 43 | t.Errorf("%d fail: error message includes reflect internals: %v", 44 | i, err) 45 | default: 46 | t.Logf("%d pass: %v", i, err) 47 | } 48 | } 49 | } 50 | 51 | type confIssue2 struct{ Main struct{ Foo string } } 52 | 53 | var testsIssue2 = []readtest{ 54 | {"[main]\n;\nfoo = bar\n", &confIssue2{struct{ Foo string }{"bar"}}, true}, 55 | {"[main]\r\n;\r\nfoo = bar\r\n", &confIssue2{struct{ Foo string }{"bar"}}, true}, 56 | } 57 | 58 | func TestIssue2(t *testing.T) { 59 | for i, tt := range testsIssue2 { 60 | id := fmt.Sprintf("issue2:%d", i) 61 | testRead(t, id, tt) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /golibs/src/code.google.com/p/gcfg/scanenum.go: -------------------------------------------------------------------------------- 1 | package gcfg 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | const ( 9 | // initial size of buffer for ScanEnum 10 | scanEnumBufferHint = 16 11 | ) 12 | 13 | func strEq(s1, s2 string, fold bool) bool { 14 | return fold && strings.EqualFold(s1, s2) || !fold && s1 == s2 15 | } 16 | 17 | // ScanEnum is a helper function to simplify the implementation of fmt.Scanner 18 | // methods for "enum-like" types, that is, user-defined types where the set of 19 | // values and string representations is fixed. 20 | // ScanEnum allows multiple string representations for the same value. 21 | // 22 | // State is the state passed to the implementation of the fmt.Scanner method. 23 | // Values holds as map values the values of the type, with their string 24 | // representations as keys. 25 | // If fold is true, comparison of the string representation uses 26 | // strings.EqualFold, otherwise the equal operator for strings. 27 | // 28 | // On a match, ScanEnum stops after reading the last rune of the matched string, 29 | // and returns the corresponding value together with a nil error. 30 | // On no match, ScanEnum attempts to unread the last rune (the first rune that 31 | // could not potentially match any of the values), and returns a non-nil error, 32 | // together with a nil value for interface{}. 33 | // On I/O error, ScanEnum returns the I/O error, together with a nil value for 34 | // interface{}. 35 | // 36 | func scanEnum(state fmt.ScanState, values map[string]interface{}, fold bool) ( 37 | interface{}, error) { 38 | // 39 | rd := make([]rune, 0, scanEnumBufferHint) 40 | keys := make(map[string]struct{}, len(values)) // potential keys 41 | for s, _ := range values { 42 | keys[s] = struct{}{} 43 | } 44 | for { 45 | r, _, err := state.ReadRune() 46 | if err != nil { 47 | return nil, err 48 | } 49 | rd = append(rd, r) 50 | srd := string(rd) 51 | lrd := len(srd) 52 | for s, _ := range keys { 53 | if strEq(srd, s, fold) { 54 | return values[s], nil 55 | } 56 | if len(rd) < len(s) && !strEq(srd, s[:lrd], fold) { 57 | delete(keys, s) 58 | } 59 | } 60 | if len(keys) == 0 { 61 | state.UnreadRune() 62 | return nil, fmt.Errorf("unsupported value %q", srd) 63 | } 64 | } 65 | panic("never reached") 66 | } 67 | -------------------------------------------------------------------------------- /golibs/src/code.google.com/p/gcfg/scanner/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package scanner 6 | 7 | import ( 8 | "fmt" 9 | "io" 10 | "sort" 11 | ) 12 | 13 | import ( 14 | "code.google.com/p/gcfg/token" 15 | ) 16 | 17 | // In an ErrorList, an error is represented by an *Error. 18 | // The position Pos, if valid, points to the beginning of 19 | // the offending token, and the error condition is described 20 | // by Msg. 21 | // 22 | type Error struct { 23 | Pos token.Position 24 | Msg string 25 | } 26 | 27 | // Error implements the error interface. 28 | func (e Error) Error() string { 29 | if e.Pos.Filename != "" || e.Pos.IsValid() { 30 | // don't print "" 31 | // TODO(gri) reconsider the semantics of Position.IsValid 32 | return e.Pos.String() + ": " + e.Msg 33 | } 34 | return e.Msg 35 | } 36 | 37 | // ErrorList is a list of *Errors. 38 | // The zero value for an ErrorList is an empty ErrorList ready to use. 39 | // 40 | type ErrorList []*Error 41 | 42 | // Add adds an Error with given position and error message to an ErrorList. 43 | func (p *ErrorList) Add(pos token.Position, msg string) { 44 | *p = append(*p, &Error{pos, msg}) 45 | } 46 | 47 | // Reset resets an ErrorList to no errors. 48 | func (p *ErrorList) Reset() { *p = (*p)[0:0] } 49 | 50 | // ErrorList implements the sort Interface. 51 | func (p ErrorList) Len() int { return len(p) } 52 | func (p ErrorList) Swap(i, j int) { p[i], p[j] = p[j], p[i] } 53 | 54 | func (p ErrorList) Less(i, j int) bool { 55 | e := &p[i].Pos 56 | f := &p[j].Pos 57 | if e.Filename < f.Filename { 58 | return true 59 | } 60 | if e.Filename == f.Filename { 61 | return e.Offset < f.Offset 62 | } 63 | return false 64 | } 65 | 66 | // Sort sorts an ErrorList. *Error entries are sorted by position, 67 | // other errors are sorted by error message, and before any *Error 68 | // entry. 69 | // 70 | func (p ErrorList) Sort() { 71 | sort.Sort(p) 72 | } 73 | 74 | // RemoveMultiples sorts an ErrorList and removes all but the first error per line. 75 | func (p *ErrorList) RemoveMultiples() { 76 | sort.Sort(p) 77 | var last token.Position // initial last.Line is != any legal error line 78 | i := 0 79 | for _, e := range *p { 80 | if e.Pos.Filename != last.Filename || e.Pos.Line != last.Line { 81 | last = e.Pos 82 | (*p)[i] = e 83 | i++ 84 | } 85 | } 86 | (*p) = (*p)[0:i] 87 | } 88 | 89 | // An ErrorList implements the error interface. 90 | func (p ErrorList) Error() string { 91 | switch len(p) { 92 | case 0: 93 | return "no errors" 94 | case 1: 95 | return p[0].Error() 96 | } 97 | return fmt.Sprintf("%s (and %d more errors)", p[0], len(p)-1) 98 | } 99 | 100 | // Err returns an error equivalent to this error list. 101 | // If the list is empty, Err returns nil. 102 | func (p ErrorList) Err() error { 103 | if len(p) == 0 { 104 | return nil 105 | } 106 | return p 107 | } 108 | 109 | // PrintError is a utility function that prints a list of errors to w, 110 | // one error per line, if the err parameter is an ErrorList. Otherwise 111 | // it prints the err string. 112 | // 113 | func PrintError(w io.Writer, err error) { 114 | if list, ok := err.(ErrorList); ok { 115 | for _, e := range list { 116 | fmt.Fprintf(w, "%s\n", e) 117 | } 118 | } else if err != nil { 119 | fmt.Fprintf(w, "%s\n", err) 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /golibs/src/code.google.com/p/gcfg/scanner/example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package scanner_test 6 | 7 | import ( 8 | "fmt" 9 | ) 10 | 11 | import ( 12 | "code.google.com/p/gcfg/scanner" 13 | "code.google.com/p/gcfg/token" 14 | ) 15 | 16 | func ExampleScanner_Scan() { 17 | // src is the input that we want to tokenize. 18 | src := []byte(`[profile "A"] 19 | color = blue ; Comment`) 20 | 21 | // Initialize the scanner. 22 | var s scanner.Scanner 23 | fset := token.NewFileSet() // positions are relative to fset 24 | file := fset.AddFile("", fset.Base(), len(src)) // register input "file" 25 | s.Init(file, src, nil /* no error handler */, scanner.ScanComments) 26 | 27 | // Repeated calls to Scan yield the token sequence found in the input. 28 | for { 29 | pos, tok, lit := s.Scan() 30 | if tok == token.EOF { 31 | break 32 | } 33 | fmt.Printf("%s\t%q\t%q\n", fset.Position(pos), tok, lit) 34 | } 35 | 36 | // output: 37 | // 1:1 "[" "" 38 | // 1:2 "IDENT" "profile" 39 | // 1:10 "STRING" "\"A\"" 40 | // 1:13 "]" "" 41 | // 1:14 "\n" "" 42 | // 2:1 "IDENT" "color" 43 | // 2:7 "=" "" 44 | // 2:9 "STRING" "blue" 45 | // 2:14 "COMMENT" "; Comment" 46 | } 47 | -------------------------------------------------------------------------------- /golibs/src/code.google.com/p/gcfg/set.go: -------------------------------------------------------------------------------- 1 | package gcfg 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "reflect" 7 | "strings" 8 | ) 9 | 10 | const ( 11 | // Default value string in case a value for a variable isn't provided. 12 | defaultValue = "true" 13 | ) 14 | 15 | func fieldFold(v reflect.Value, name string) reflect.Value { 16 | n := strings.Replace(name, "-", "_", -1) 17 | return v.FieldByNameFunc(func(fieldName string) bool { 18 | return strings.EqualFold(n, fieldName) 19 | }) 20 | } 21 | 22 | func set(cfg interface{}, sect, sub, name, value string) error { 23 | vPCfg := reflect.ValueOf(cfg) 24 | if vPCfg.Kind() != reflect.Ptr || vPCfg.Elem().Kind() != reflect.Struct { 25 | panic(fmt.Errorf("config must be a pointer to a struct")) 26 | } 27 | vCfg := vPCfg.Elem() 28 | vSect := fieldFold(vCfg, sect) 29 | if !vSect.IsValid() { 30 | return fmt.Errorf("invalid section: section %q", sect) 31 | } 32 | if vSect.Kind() == reflect.Map { 33 | vst := vSect.Type() 34 | if vst.Key().Kind() != reflect.String || 35 | vst.Elem().Kind() != reflect.Ptr || 36 | vst.Elem().Elem().Kind() != reflect.Struct { 37 | panic(fmt.Errorf("map field for section must have string keys and "+ 38 | " pointer-to-struct values: section %q", sect)) 39 | } 40 | if vSect.IsNil() { 41 | vSect.Set(reflect.MakeMap(vst)) 42 | } 43 | k := reflect.ValueOf(sub) 44 | pv := vSect.MapIndex(k) 45 | if !pv.IsValid() { 46 | vType := vSect.Type().Elem().Elem() 47 | pv = reflect.New(vType) 48 | vSect.SetMapIndex(k, pv) 49 | } 50 | vSect = pv.Elem() 51 | } else if vSect.Kind() != reflect.Struct { 52 | panic(fmt.Errorf("field for section must be a map or a struct: "+ 53 | "section %q", sect)) 54 | } else if sub != "" { 55 | return fmt.Errorf("invalid subsection: "+ 56 | "section %q subsection %q", sect, sub) 57 | } 58 | vName := fieldFold(vSect, name) 59 | if !vName.IsValid() { 60 | return fmt.Errorf("invalid variable: "+ 61 | "section %q subsection %q variable %q", sect, sub, name) 62 | } 63 | vAddr := vName.Addr().Interface() 64 | switch v := vAddr.(type) { 65 | case *string: 66 | *v = value 67 | return nil 68 | case *bool: 69 | vAddr = (*gbool)(v) 70 | } 71 | // attempt to read an extra rune to make sure the value is consumed 72 | var r rune 73 | n, err := fmt.Sscanf(value, "%v%c", vAddr, &r) 74 | switch { 75 | case n < 1 || n == 1 && err != io.EOF: 76 | return fmt.Errorf("failed to parse %q as %v: %v", value, vName.Type(), 77 | err) 78 | case n > 1: 79 | return fmt.Errorf("failed to parse %q as %v: extra characters", value, 80 | vName.Type()) 81 | case n == 1 && err == io.EOF: 82 | return nil 83 | } 84 | panic("never reached") 85 | } 86 | -------------------------------------------------------------------------------- /golibs/src/code.google.com/p/gcfg/testdata/gcfg_test.gcfg: -------------------------------------------------------------------------------- 1 | ; Comment line 2 | [section] 3 | name=value # comment 4 | -------------------------------------------------------------------------------- /golibs/src/code.google.com/p/gcfg/token/position_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package token 6 | 7 | import ( 8 | "fmt" 9 | "testing" 10 | ) 11 | 12 | func checkPos(t *testing.T, msg string, p, q Position) { 13 | if p.Filename != q.Filename { 14 | t.Errorf("%s: expected filename = %q; got %q", msg, q.Filename, p.Filename) 15 | } 16 | if p.Offset != q.Offset { 17 | t.Errorf("%s: expected offset = %d; got %d", msg, q.Offset, p.Offset) 18 | } 19 | if p.Line != q.Line { 20 | t.Errorf("%s: expected line = %d; got %d", msg, q.Line, p.Line) 21 | } 22 | if p.Column != q.Column { 23 | t.Errorf("%s: expected column = %d; got %d", msg, q.Column, p.Column) 24 | } 25 | } 26 | 27 | func TestNoPos(t *testing.T) { 28 | if NoPos.IsValid() { 29 | t.Errorf("NoPos should not be valid") 30 | } 31 | var fset *FileSet 32 | checkPos(t, "nil NoPos", fset.Position(NoPos), Position{}) 33 | fset = NewFileSet() 34 | checkPos(t, "fset NoPos", fset.Position(NoPos), Position{}) 35 | } 36 | 37 | var tests = []struct { 38 | filename string 39 | source []byte // may be nil 40 | size int 41 | lines []int 42 | }{ 43 | {"a", []byte{}, 0, []int{}}, 44 | {"b", []byte("01234"), 5, []int{0}}, 45 | {"c", []byte("\n\n\n\n\n\n\n\n\n"), 9, []int{0, 1, 2, 3, 4, 5, 6, 7, 8}}, 46 | {"d", nil, 100, []int{0, 5, 10, 20, 30, 70, 71, 72, 80, 85, 90, 99}}, 47 | {"e", nil, 777, []int{0, 80, 100, 120, 130, 180, 267, 455, 500, 567, 620}}, 48 | {"f", []byte("package p\n\nimport \"fmt\""), 23, []int{0, 10, 11}}, 49 | {"g", []byte("package p\n\nimport \"fmt\"\n"), 24, []int{0, 10, 11}}, 50 | {"h", []byte("package p\n\nimport \"fmt\"\n "), 25, []int{0, 10, 11, 24}}, 51 | } 52 | 53 | func linecol(lines []int, offs int) (int, int) { 54 | prevLineOffs := 0 55 | for line, lineOffs := range lines { 56 | if offs < lineOffs { 57 | return line, offs - prevLineOffs + 1 58 | } 59 | prevLineOffs = lineOffs 60 | } 61 | return len(lines), offs - prevLineOffs + 1 62 | } 63 | 64 | func verifyPositions(t *testing.T, fset *FileSet, f *File, lines []int) { 65 | for offs := 0; offs < f.Size(); offs++ { 66 | p := f.Pos(offs) 67 | offs2 := f.Offset(p) 68 | if offs2 != offs { 69 | t.Errorf("%s, Offset: expected offset %d; got %d", f.Name(), offs, offs2) 70 | } 71 | line, col := linecol(lines, offs) 72 | msg := fmt.Sprintf("%s (offs = %d, p = %d)", f.Name(), offs, p) 73 | checkPos(t, msg, f.Position(f.Pos(offs)), Position{f.Name(), offs, line, col}) 74 | checkPos(t, msg, fset.Position(p), Position{f.Name(), offs, line, col}) 75 | } 76 | } 77 | 78 | func makeTestSource(size int, lines []int) []byte { 79 | src := make([]byte, size) 80 | for _, offs := range lines { 81 | if offs > 0 { 82 | src[offs-1] = '\n' 83 | } 84 | } 85 | return src 86 | } 87 | 88 | func TestPositions(t *testing.T) { 89 | const delta = 7 // a non-zero base offset increment 90 | fset := NewFileSet() 91 | for _, test := range tests { 92 | // verify consistency of test case 93 | if test.source != nil && len(test.source) != test.size { 94 | t.Errorf("%s: inconsistent test case: expected file size %d; got %d", test.filename, test.size, len(test.source)) 95 | } 96 | 97 | // add file and verify name and size 98 | f := fset.AddFile(test.filename, fset.Base()+delta, test.size) 99 | if f.Name() != test.filename { 100 | t.Errorf("expected filename %q; got %q", test.filename, f.Name()) 101 | } 102 | if f.Size() != test.size { 103 | t.Errorf("%s: expected file size %d; got %d", f.Name(), test.size, f.Size()) 104 | } 105 | if fset.File(f.Pos(0)) != f { 106 | t.Errorf("%s: f.Pos(0) was not found in f", f.Name()) 107 | } 108 | 109 | // add lines individually and verify all positions 110 | for i, offset := range test.lines { 111 | f.AddLine(offset) 112 | if f.LineCount() != i+1 { 113 | t.Errorf("%s, AddLine: expected line count %d; got %d", f.Name(), i+1, f.LineCount()) 114 | } 115 | // adding the same offset again should be ignored 116 | f.AddLine(offset) 117 | if f.LineCount() != i+1 { 118 | t.Errorf("%s, AddLine: expected unchanged line count %d; got %d", f.Name(), i+1, f.LineCount()) 119 | } 120 | verifyPositions(t, fset, f, test.lines[0:i+1]) 121 | } 122 | 123 | // add lines with SetLines and verify all positions 124 | if ok := f.SetLines(test.lines); !ok { 125 | t.Errorf("%s: SetLines failed", f.Name()) 126 | } 127 | if f.LineCount() != len(test.lines) { 128 | t.Errorf("%s, SetLines: expected line count %d; got %d", f.Name(), len(test.lines), f.LineCount()) 129 | } 130 | verifyPositions(t, fset, f, test.lines) 131 | 132 | // add lines with SetLinesForContent and verify all positions 133 | src := test.source 134 | if src == nil { 135 | // no test source available - create one from scratch 136 | src = makeTestSource(test.size, test.lines) 137 | } 138 | f.SetLinesForContent(src) 139 | if f.LineCount() != len(test.lines) { 140 | t.Errorf("%s, SetLinesForContent: expected line count %d; got %d", f.Name(), len(test.lines), f.LineCount()) 141 | } 142 | verifyPositions(t, fset, f, test.lines) 143 | } 144 | } 145 | 146 | func TestLineInfo(t *testing.T) { 147 | fset := NewFileSet() 148 | f := fset.AddFile("foo", fset.Base(), 500) 149 | lines := []int{0, 42, 77, 100, 210, 220, 277, 300, 333, 401} 150 | // add lines individually and provide alternative line information 151 | for _, offs := range lines { 152 | f.AddLine(offs) 153 | f.AddLineInfo(offs, "bar", 42) 154 | } 155 | // verify positions for all offsets 156 | for offs := 0; offs <= f.Size(); offs++ { 157 | p := f.Pos(offs) 158 | _, col := linecol(lines, offs) 159 | msg := fmt.Sprintf("%s (offs = %d, p = %d)", f.Name(), offs, p) 160 | checkPos(t, msg, f.Position(f.Pos(offs)), Position{"bar", offs, 42, col}) 161 | checkPos(t, msg, fset.Position(p), Position{"bar", offs, 42, col}) 162 | } 163 | } 164 | 165 | func TestFiles(t *testing.T) { 166 | fset := NewFileSet() 167 | for i, test := range tests { 168 | fset.AddFile(test.filename, fset.Base(), test.size) 169 | j := 0 170 | fset.Iterate(func(f *File) bool { 171 | if f.Name() != tests[j].filename { 172 | t.Errorf("expected filename = %s; got %s", tests[j].filename, f.Name()) 173 | } 174 | j++ 175 | return true 176 | }) 177 | if j != i+1 { 178 | t.Errorf("expected %d files; got %d", i+1, j) 179 | } 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /golibs/src/code.google.com/p/gcfg/token/serialize.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package token 6 | 7 | type serializedFile struct { 8 | // fields correspond 1:1 to fields with same (lower-case) name in File 9 | Name string 10 | Base int 11 | Size int 12 | Lines []int 13 | Infos []lineInfo 14 | } 15 | 16 | type serializedFileSet struct { 17 | Base int 18 | Files []serializedFile 19 | } 20 | 21 | // Read calls decode to deserialize a file set into s; s must not be nil. 22 | func (s *FileSet) Read(decode func(interface{}) error) error { 23 | var ss serializedFileSet 24 | if err := decode(&ss); err != nil { 25 | return err 26 | } 27 | 28 | s.mutex.Lock() 29 | s.base = ss.Base 30 | files := make([]*File, len(ss.Files)) 31 | for i := 0; i < len(ss.Files); i++ { 32 | f := &ss.Files[i] 33 | files[i] = &File{s, f.Name, f.Base, f.Size, f.Lines, f.Infos} 34 | } 35 | s.files = files 36 | s.last = nil 37 | s.mutex.Unlock() 38 | 39 | return nil 40 | } 41 | 42 | // Write calls encode to serialize the file set s. 43 | func (s *FileSet) Write(encode func(interface{}) error) error { 44 | var ss serializedFileSet 45 | 46 | s.mutex.Lock() 47 | ss.Base = s.base 48 | files := make([]serializedFile, len(s.files)) 49 | for i, f := range s.files { 50 | files[i] = serializedFile{f.name, f.base, f.size, f.lines, f.infos} 51 | } 52 | ss.Files = files 53 | s.mutex.Unlock() 54 | 55 | return encode(ss) 56 | } 57 | -------------------------------------------------------------------------------- /golibs/src/code.google.com/p/gcfg/token/serialize_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package token 6 | 7 | import ( 8 | "bytes" 9 | "encoding/gob" 10 | "fmt" 11 | "testing" 12 | ) 13 | 14 | // equal returns nil if p and q describe the same file set; 15 | // otherwise it returns an error describing the discrepancy. 16 | func equal(p, q *FileSet) error { 17 | if p == q { 18 | // avoid deadlock if p == q 19 | return nil 20 | } 21 | 22 | // not strictly needed for the test 23 | p.mutex.Lock() 24 | q.mutex.Lock() 25 | defer q.mutex.Unlock() 26 | defer p.mutex.Unlock() 27 | 28 | if p.base != q.base { 29 | return fmt.Errorf("different bases: %d != %d", p.base, q.base) 30 | } 31 | 32 | if len(p.files) != len(q.files) { 33 | return fmt.Errorf("different number of files: %d != %d", len(p.files), len(q.files)) 34 | } 35 | 36 | for i, f := range p.files { 37 | g := q.files[i] 38 | if f.set != p { 39 | return fmt.Errorf("wrong fileset for %q", f.name) 40 | } 41 | if g.set != q { 42 | return fmt.Errorf("wrong fileset for %q", g.name) 43 | } 44 | if f.name != g.name { 45 | return fmt.Errorf("different filenames: %q != %q", f.name, g.name) 46 | } 47 | if f.base != g.base { 48 | return fmt.Errorf("different base for %q: %d != %d", f.name, f.base, g.base) 49 | } 50 | if f.size != g.size { 51 | return fmt.Errorf("different size for %q: %d != %d", f.name, f.size, g.size) 52 | } 53 | for j, l := range f.lines { 54 | m := g.lines[j] 55 | if l != m { 56 | return fmt.Errorf("different offsets for %q", f.name) 57 | } 58 | } 59 | for j, l := range f.infos { 60 | m := g.infos[j] 61 | if l.Offset != m.Offset || l.Filename != m.Filename || l.Line != m.Line { 62 | return fmt.Errorf("different infos for %q", f.name) 63 | } 64 | } 65 | } 66 | 67 | // we don't care about .last - it's just a cache 68 | return nil 69 | } 70 | 71 | func checkSerialize(t *testing.T, p *FileSet) { 72 | var buf bytes.Buffer 73 | encode := func(x interface{}) error { 74 | return gob.NewEncoder(&buf).Encode(x) 75 | } 76 | if err := p.Write(encode); err != nil { 77 | t.Errorf("writing fileset failed: %s", err) 78 | return 79 | } 80 | q := NewFileSet() 81 | decode := func(x interface{}) error { 82 | return gob.NewDecoder(&buf).Decode(x) 83 | } 84 | if err := q.Read(decode); err != nil { 85 | t.Errorf("reading fileset failed: %s", err) 86 | return 87 | } 88 | if err := equal(p, q); err != nil { 89 | t.Errorf("filesets not identical: %s", err) 90 | } 91 | } 92 | 93 | func TestSerialization(t *testing.T) { 94 | p := NewFileSet() 95 | checkSerialize(t, p) 96 | // add some files 97 | for i := 0; i < 10; i++ { 98 | f := p.AddFile(fmt.Sprintf("file%d", i), p.Base()+i, i*100) 99 | checkSerialize(t, p) 100 | // add some lines and alternative file infos 101 | line := 1000 102 | for offs := 0; offs < f.Size(); offs += 40 + i { 103 | f.AddLine(offs) 104 | if offs%7 == 0 { 105 | f.AddLineInfo(offs, fmt.Sprintf("file%d", offs), line) 106 | line += 33 107 | } 108 | } 109 | checkSerialize(t, p) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /golibs/src/code.google.com/p/gcfg/token/token.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package token defines constants representing the lexical tokens of the gcfg 6 | // configuration syntax and basic operations on tokens (printing, predicates). 7 | // 8 | // Note that the API for the token package may change to accommodate new 9 | // features or implementation changes in gcfg. 10 | // 11 | package token 12 | 13 | import "strconv" 14 | 15 | // Token is the set of lexical tokens of the gcfg configuration syntax. 16 | type Token int 17 | 18 | // The list of tokens. 19 | const ( 20 | // Special tokens 21 | ILLEGAL Token = iota 22 | EOF 23 | COMMENT 24 | 25 | literal_beg 26 | // Identifiers and basic type literals 27 | // (these tokens stand for classes of literals) 28 | IDENT // section-name, variable-name 29 | STRING // "subsection-name", variable value 30 | literal_end 31 | 32 | operator_beg 33 | // Operators and delimiters 34 | ASSIGN // = 35 | LBRACK // [ 36 | RBRACK // ] 37 | EOL // \n 38 | operator_end 39 | ) 40 | 41 | var tokens = [...]string{ 42 | ILLEGAL: "ILLEGAL", 43 | 44 | EOF: "EOF", 45 | COMMENT: "COMMENT", 46 | 47 | IDENT: "IDENT", 48 | STRING: "STRING", 49 | 50 | ASSIGN: "=", 51 | LBRACK: "[", 52 | RBRACK: "]", 53 | EOL: "\n", 54 | } 55 | 56 | // String returns the string corresponding to the token tok. 57 | // For operators and delimiters, the string is the actual token character 58 | // sequence (e.g., for the token ASSIGN, the string is "="). For all other 59 | // tokens the string corresponds to the token constant name (e.g. for the 60 | // token IDENT, the string is "IDENT"). 61 | // 62 | func (tok Token) String() string { 63 | s := "" 64 | if 0 <= tok && tok < Token(len(tokens)) { 65 | s = tokens[tok] 66 | } 67 | if s == "" { 68 | s = "token(" + strconv.Itoa(int(tok)) + ")" 69 | } 70 | return s 71 | } 72 | 73 | // Predicates 74 | 75 | // IsLiteral returns true for tokens corresponding to identifiers 76 | // and basic type literals; it returns false otherwise. 77 | // 78 | func (tok Token) IsLiteral() bool { return literal_beg < tok && tok < literal_end } 79 | 80 | // IsOperator returns true for tokens corresponding to operators and 81 | // delimiters; it returns false otherwise. 82 | // 83 | func (tok Token) IsOperator() bool { return operator_beg < tok && tok < operator_end } 84 | -------------------------------------------------------------------------------- /golibs/src/github.com/abbot/go-http-auth/.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.a 3 | *.6 4 | *.out 5 | _testmain.go 6 | -------------------------------------------------------------------------------- /golibs/src/github.com/abbot/go-http-auth/Makefile: -------------------------------------------------------------------------------- 1 | include $(GOROOT)/src/Make.inc 2 | 3 | TARG=auth_digest 4 | GOFILES=\ 5 | auth.go\ 6 | digest.go\ 7 | basic.go\ 8 | misc.go\ 9 | md5crypt.go\ 10 | users.go\ 11 | 12 | include $(GOROOT)/src/Make.pkg 13 | -------------------------------------------------------------------------------- /golibs/src/github.com/abbot/go-http-auth/README.md: -------------------------------------------------------------------------------- 1 | HTTP Authentication implementation in Go 2 | ======================================== 3 | 4 | This is an implementation of HTTP Basic and HTTP Digest authentication 5 | in Go language. It is designed as a simple wrapper for 6 | http.RequestHandler functions. 7 | 8 | Features 9 | -------- 10 | 11 | * Supports HTTP Basic and HTTP Digest authentication. 12 | * Supports htpasswd and htdigest formatted files. 13 | * Automatic reloading of password files. 14 | * Pluggable interface for user/password storage. 15 | * Supports MD5 and SHA1 for Basic authentication password storage. 16 | * Configurable Digest nonce cache size with expiration. 17 | * Wrapper for legacy http handlers (http.HandlerFunc interface) 18 | 19 | Example usage 20 | ------------- 21 | 22 | This is a complete working example for Basic auth: 23 | 24 | package main 25 | 26 | import ( 27 | auth "github.com/abbot/go-http-auth" 28 | "fmt" 29 | "net/http" 30 | ) 31 | 32 | func Secret(user, realm string) string { 33 | if user == "john" { 34 | // password is "hello" 35 | return "$1$dlPL2MqE$oQmn16q49SqdmhenQuNgs1" 36 | } 37 | return "" 38 | } 39 | 40 | func handle(w http.ResponseWriter, r *auth.AuthenticatedRequest) { 41 | fmt.Fprintf(w, "

Hello, %s!

", r.Username) 42 | } 43 | 44 | func main() { 45 | authenticator := auth.NewBasicAuthenticator("example.com", Secret) 46 | http.HandleFunc("/", authenticator.Wrap(handle)) 47 | http.ListenAndServe(":8080", nil) 48 | } 49 | 50 | See more examples in the "examples" directory. 51 | 52 | Legal 53 | ----- 54 | 55 | This module is developed under Apache 2.0 license, and can be used for 56 | open and proprietary projects. 57 | 58 | Copyright 2012-2013 Lev Shamardin 59 | 60 | Licensed under the Apache License, Version 2.0 (the "License"); you 61 | may not use this file or any other part of this project except in 62 | compliance with the License. You may obtain a copy of the License at 63 | 64 | http://www.apache.org/licenses/LICENSE-2.0 65 | 66 | Unless required by applicable law or agreed to in writing, software 67 | distributed under the License is distributed on an "AS IS" BASIS, 68 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 69 | implied. See the License for the specific language governing 70 | permissions and limitations under the License. 71 | -------------------------------------------------------------------------------- /golibs/src/github.com/abbot/go-http-auth/auth.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import "net/http" 4 | 5 | /* 6 | Request handlers must take AuthenticatedRequest instead of http.Request 7 | */ 8 | type AuthenticatedRequest struct { 9 | http.Request 10 | /* 11 | Authenticated user name. Current API implies that Username is 12 | never empty, which means that authentication is always done 13 | before calling the request handler. 14 | */ 15 | Username string 16 | } 17 | 18 | /* 19 | AuthenticatedHandlerFunc is like http.HandlerFunc, but takes 20 | AuthenticatedRequest instead of http.Request 21 | */ 22 | type AuthenticatedHandlerFunc func(http.ResponseWriter, *AuthenticatedRequest) 23 | 24 | /* 25 | Authenticator wraps an AuthenticatedHandlerFunc with 26 | authentication-checking code. 27 | 28 | Typical Authenticator usage is something like: 29 | 30 | authenticator := SomeAuthenticator(...) 31 | http.HandleFunc("/", authenticator(my_handler)) 32 | 33 | Authenticator wrapper checks the user authentication and calls the 34 | wrapped function only after authentication has succeeded. Otherwise, 35 | it returns a handler which initiates the authentication procedure. 36 | */ 37 | type Authenticator func(AuthenticatedHandlerFunc) http.HandlerFunc 38 | 39 | type AuthenticatorInterface interface { 40 | Wrap(AuthenticatedHandlerFunc) http.HandlerFunc 41 | } 42 | 43 | func JustCheck(auth AuthenticatorInterface, wrapped http.HandlerFunc) http.HandlerFunc { 44 | return auth.Wrap(func(w http.ResponseWriter, ar *AuthenticatedRequest) { 45 | ar.Header.Set("X-Authenticated-Username", ar.Username) 46 | wrapped(w, &ar.Request) 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /golibs/src/github.com/abbot/go-http-auth/basic.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "crypto/sha1" 5 | "encoding/base64" 6 | "net/http" 7 | "strings" 8 | ) 9 | 10 | type BasicAuth struct { 11 | Realm string 12 | Secrets SecretProvider 13 | } 14 | 15 | /* 16 | Checks the username/password combination from the request. Returns 17 | either an empty string (authentication failed) or the name of the 18 | authenticated user. 19 | 20 | Supports MD5 and SHA1 password entries 21 | */ 22 | func (a *BasicAuth) CheckAuth(r *http.Request) string { 23 | s := strings.SplitN(r.Header.Get("Authorization"), " ", 2) 24 | if len(s) != 2 || s[0] != "Basic" { 25 | return "" 26 | } 27 | 28 | b, err := base64.StdEncoding.DecodeString(s[1]) 29 | if err != nil { 30 | return "" 31 | } 32 | pair := strings.SplitN(string(b), ":", 2) 33 | if len(pair) != 2 { 34 | return "" 35 | } 36 | passwd := a.Secrets(pair[0], a.Realm) 37 | if passwd == "" { 38 | return "" 39 | } 40 | if passwd[:5] == "{SHA}" { 41 | d := sha1.New() 42 | d.Write([]byte(pair[1])) 43 | if passwd[5:] != base64.StdEncoding.EncodeToString(d.Sum(nil)) { 44 | return "" 45 | } 46 | } else { 47 | e := NewMD5Entry(passwd) 48 | if e == nil { 49 | return "" 50 | } 51 | if passwd != string(MD5Crypt([]byte(pair[1]), e.Salt, e.Magic)) { 52 | return "" 53 | } 54 | } 55 | return pair[0] 56 | } 57 | 58 | /* 59 | http.Handler for BasicAuth which initiates the authentication process 60 | (or requires reauthentication). 61 | */ 62 | func (a *BasicAuth) RequireAuth(w http.ResponseWriter, r *http.Request) { 63 | w.Header().Set("WWW-Authenticate", `Basic realm="`+a.Realm+`"`) 64 | w.WriteHeader(401) 65 | w.Write([]byte("401 Unauthorized\n")) 66 | } 67 | 68 | /* 69 | BasicAuthenticator returns a function, which wraps an 70 | AuthenticatedHandlerFunc converting it to http.HandlerFunc. This 71 | wrapper function checks the authentication and either sends back 72 | required authentication headers, or calls the wrapped function with 73 | authenticated username in the AuthenticatedRequest. 74 | */ 75 | func (a *BasicAuth) Wrap(wrapped AuthenticatedHandlerFunc) http.HandlerFunc { 76 | return func(w http.ResponseWriter, r *http.Request) { 77 | if username := a.CheckAuth(r); username == "" { 78 | a.RequireAuth(w, r) 79 | } else { 80 | ar := &AuthenticatedRequest{Request: *r, Username: username} 81 | wrapped(w, ar) 82 | } 83 | } 84 | } 85 | 86 | func NewBasicAuthenticator(realm string, secrets SecretProvider) *BasicAuth { 87 | return &BasicAuth{Realm: realm, Secrets: secrets} 88 | } 89 | 90 | func (a *BasicAuth) WrapHandler(wrapped http.Handler) http.Handler { 91 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 92 | if username := a.CheckAuth(r); username == "" { 93 | a.RequireAuth(w, r) 94 | } else { 95 | wrapped.ServeHTTP(w, r) 96 | } 97 | }) 98 | } 99 | -------------------------------------------------------------------------------- /golibs/src/github.com/abbot/go-http-auth/basic_test.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "encoding/base64" 5 | "net/http" 6 | "testing" 7 | ) 8 | 9 | func TestAuthBasic(t *testing.T) { 10 | secrets := HtpasswdFileProvider("test.htpasswd") 11 | a := &BasicAuth{Realm: "example.com", Secrets: secrets} 12 | r := &http.Request{} 13 | r.Method = "GET" 14 | if a.CheckAuth(r) != "" { 15 | t.Fatal("CheckAuth passed on empty headers") 16 | } 17 | r.Header = http.Header(make(map[string][]string)) 18 | r.Header.Set("Authorization", "Digest blabla ololo") 19 | if a.CheckAuth(r) != "" { 20 | t.Fatal("CheckAuth passed on bad headers") 21 | } 22 | r.Header.Set("Authorization", "Basic !@#") 23 | if a.CheckAuth(r) != "" { 24 | t.Fatal("CheckAuth passed on bad base64 data") 25 | } 26 | 27 | data := [][]string{ 28 | {"test", "hello"}, 29 | {"test2", "hello2"}, 30 | } 31 | for _, tc := range data { 32 | auth := base64.StdEncoding.EncodeToString([]byte(tc[0] + ":" + tc[1])) 33 | r.Header.Set("Authorization", "Basic "+auth) 34 | if a.CheckAuth(r) != tc[0] { 35 | t.Fatalf("CheckAuth failed for user '%s'", tc[0]) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /golibs/src/github.com/abbot/go-http-auth/digest.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "net/url" 7 | "sort" 8 | "strconv" 9 | "strings" 10 | "sync" 11 | "time" 12 | ) 13 | 14 | type digest_client struct { 15 | nc uint64 16 | last_seen int64 17 | } 18 | 19 | type DigestAuth struct { 20 | Realm string 21 | Opaque string 22 | Secrets SecretProvider 23 | PlainTextSecrets bool 24 | 25 | /* 26 | Approximate size of Client's Cache. When actual number of 27 | tracked client nonces exceeds 28 | ClientCacheSize+ClientCacheTolerance, ClientCacheTolerance*2 29 | older entries are purged. 30 | */ 31 | ClientCacheSize int 32 | ClientCacheTolerance int 33 | 34 | clients map[string]*digest_client 35 | mutex sync.Mutex 36 | } 37 | 38 | type digest_cache_entry struct { 39 | nonce string 40 | last_seen int64 41 | } 42 | 43 | type digest_cache []digest_cache_entry 44 | 45 | func (c digest_cache) Less(i, j int) bool { 46 | return c[i].last_seen < c[j].last_seen 47 | } 48 | 49 | func (c digest_cache) Len() int { 50 | return len(c) 51 | } 52 | 53 | func (c digest_cache) Swap(i, j int) { 54 | c[i], c[j] = c[j], c[i] 55 | } 56 | 57 | /* 58 | Remove count oldest entries from DigestAuth.clients 59 | */ 60 | func (a *DigestAuth) Purge(count int) { 61 | entries := make([]digest_cache_entry, 0, len(a.clients)) 62 | for nonce, client := range a.clients { 63 | entries = append(entries, digest_cache_entry{nonce, client.last_seen}) 64 | } 65 | cache := digest_cache(entries) 66 | sort.Sort(cache) 67 | for _, client := range cache[:count] { 68 | delete(a.clients, client.nonce) 69 | } 70 | } 71 | 72 | /* 73 | http.Handler for DigestAuth which initiates the authentication process 74 | (or requires reauthentication). 75 | */ 76 | func (a *DigestAuth) RequireAuth(w http.ResponseWriter, r *http.Request) { 77 | if len(a.clients) > a.ClientCacheSize+a.ClientCacheTolerance { 78 | a.Purge(a.ClientCacheTolerance * 2) 79 | } 80 | nonce := RandomKey() 81 | a.clients[nonce] = &digest_client{nc: 0, last_seen: time.Now().UnixNano()} 82 | w.Header().Set("WWW-Authenticate", 83 | fmt.Sprintf(`Digest realm="%s", nonce="%s", opaque="%s", algorithm="MD5", qop="auth"`, 84 | a.Realm, nonce, a.Opaque)) 85 | w.WriteHeader(401) 86 | w.Write([]byte("401 Unauthorized\n")) 87 | } 88 | 89 | /* 90 | Parse Authorization header from the http.Request. Returns a map of 91 | auth parameters or nil if the header is not a valid parsable Digest 92 | auth header. 93 | */ 94 | func DigestAuthParams(r *http.Request) map[string]string { 95 | s := strings.SplitN(r.Header.Get("Authorization"), " ", 2) 96 | if len(s) != 2 || s[0] != "Digest" { 97 | return nil 98 | } 99 | 100 | result := map[string]string{} 101 | for _, kv := range strings.Split(s[1], ",") { 102 | parts := strings.SplitN(kv, "=", 2) 103 | if len(parts) != 2 { 104 | continue 105 | } 106 | result[strings.Trim(parts[0], "\" ")] = strings.Trim(parts[1], "\" ") 107 | } 108 | return result 109 | } 110 | 111 | /* 112 | Check if request contains valid authentication data. Returns a pair 113 | of username, authinfo where username is the name of the authenticated 114 | user or an empty string and authinfo is the contents for the optional 115 | Authentication-Info response header. 116 | */ 117 | func (da *DigestAuth) CheckAuth(r *http.Request) (username string, authinfo *string) { 118 | da.mutex.Lock() 119 | defer da.mutex.Unlock() 120 | username = "" 121 | authinfo = nil 122 | auth := DigestAuthParams(r) 123 | if auth == nil || da.Opaque != auth["opaque"] || auth["algorithm"] != "MD5" || auth["qop"] != "auth" { 124 | return 125 | } 126 | 127 | // Check if the requested URI matches auth header 128 | switch u, err := url.Parse(auth["uri"]); { 129 | case err != nil: 130 | return 131 | case r.URL == nil: 132 | return 133 | case len(u.Path) > len(r.URL.Path): 134 | return 135 | case !strings.HasPrefix(r.URL.Path, u.Path): 136 | return 137 | } 138 | 139 | HA1 := da.Secrets(auth["username"], da.Realm) 140 | if da.PlainTextSecrets { 141 | HA1 = H(auth["username"] + ":" + da.Realm + ":" + HA1) 142 | } 143 | HA2 := H(r.Method + ":" + auth["uri"]) 144 | KD := H(strings.Join([]string{HA1, auth["nonce"], auth["nc"], auth["cnonce"], auth["qop"], HA2}, ":")) 145 | 146 | if KD != auth["response"] { 147 | return 148 | } 149 | 150 | // At this point crypto checks are completed and validated. 151 | // Now check if the session is valid. 152 | 153 | nc, err := strconv.ParseUint(auth["nc"], 16, 64) 154 | if err != nil { 155 | return 156 | } 157 | 158 | if client, ok := da.clients[auth["nonce"]]; !ok { 159 | return 160 | } else { 161 | if client.nc != 0 && client.nc >= nc { 162 | return 163 | } 164 | client.nc = nc 165 | client.last_seen = time.Now().UnixNano() 166 | } 167 | 168 | resp_HA2 := H(":" + auth["uri"]) 169 | rspauth := H(strings.Join([]string{HA1, auth["nonce"], auth["nc"], auth["cnonce"], auth["qop"], resp_HA2}, ":")) 170 | 171 | info := fmt.Sprintf(`qop="auth", rspauth="%s", cnonce="%s", nc="%s"`, rspauth, auth["cnonce"], auth["nc"]) 172 | return auth["username"], &info 173 | } 174 | 175 | /* 176 | Default values for ClientCacheSize and ClientCacheTolerance for DigestAuth 177 | */ 178 | const DefaultClientCacheSize = 1000 179 | const DefaultClientCacheTolerance = 100 180 | 181 | /* 182 | Wrap returns an Authenticator which uses HTTP Digest 183 | authentication. Arguments: 184 | 185 | realm: The authentication realm. 186 | 187 | secrets: SecretProvider which must return HA1 digests for the same 188 | realm as above. 189 | */ 190 | func (a *DigestAuth) Wrap(wrapped AuthenticatedHandlerFunc) http.HandlerFunc { 191 | return func(w http.ResponseWriter, r *http.Request) { 192 | if username, authinfo := a.CheckAuth(r); username == "" { 193 | a.RequireAuth(w, r) 194 | } else { 195 | ar := &AuthenticatedRequest{Request: *r, Username: username} 196 | if authinfo != nil { 197 | w.Header().Set("Authentication-Info", *authinfo) 198 | } 199 | wrapped(w, ar) 200 | } 201 | } 202 | } 203 | 204 | /* 205 | JustCheck returns function which converts an http.HandlerFunc into a 206 | http.HandlerFunc which requires authentication. Username is passed as 207 | an extra X-Authenticated-Username header. 208 | */ 209 | func (a *DigestAuth) JustCheck(wrapped http.HandlerFunc) http.HandlerFunc { 210 | return a.Wrap(func(w http.ResponseWriter, ar *AuthenticatedRequest) { 211 | ar.Header.Set("X-Authenticated-Username", ar.Username) 212 | wrapped(w, &ar.Request) 213 | }) 214 | } 215 | 216 | func NewDigestAuthenticator(realm string, secrets SecretProvider) *DigestAuth { 217 | da := &DigestAuth{ 218 | Opaque: RandomKey(), 219 | Realm: realm, 220 | Secrets: secrets, 221 | PlainTextSecrets: false, 222 | ClientCacheSize: DefaultClientCacheSize, 223 | ClientCacheTolerance: DefaultClientCacheTolerance, 224 | clients: map[string]*digest_client{}} 225 | return da 226 | } 227 | -------------------------------------------------------------------------------- /golibs/src/github.com/abbot/go-http-auth/digest_test.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "net/http" 5 | "net/url" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestAuthDigest(t *testing.T) { 11 | secrets := HtdigestFileProvider("test.htdigest") 12 | da := &DigestAuth{Opaque: "U7H+ier3Ae8Skd/g", 13 | Realm: "example.com", 14 | Secrets: secrets, 15 | clients: map[string]*digest_client{}} 16 | r := &http.Request{} 17 | r.Method = "GET" 18 | if u, _ := da.CheckAuth(r); u != "" { 19 | t.Fatal("non-empty auth for empty request header") 20 | } 21 | r.Header = http.Header(make(map[string][]string)) 22 | r.Header.Set("Authorization", "Digest blabla") 23 | if u, _ := da.CheckAuth(r); u != "" { 24 | t.Fatal("non-empty auth for bad request header") 25 | } 26 | r.Header.Set("Authorization", `Digest username="test", realm="example.com", nonce="Vb9BP/h81n3GpTTB", uri="/t2", cnonce="NjE4MTM2", nc=00000001, qop="auth", response="ffc357c4eba74773c8687e0bc724c9a3", opaque="U7H+ier3Ae8Skd/g", algorithm="MD5"`) 27 | if u, _ := da.CheckAuth(r); u != "" { 28 | t.Fatal("non-empty auth for unknown client") 29 | } 30 | 31 | r.URL, _ = url.Parse("/t2") 32 | da.clients["Vb9BP/h81n3GpTTB"] = &digest_client{nc: 0, last_seen: time.Now().UnixNano()} 33 | if u, _ := da.CheckAuth(r); u != "test" { 34 | t.Fatal("empty auth for legitimate client") 35 | } 36 | if u, _ := da.CheckAuth(r); u != "" { 37 | t.Fatal("non-empty auth for outdated nc") 38 | } 39 | 40 | r.URL, _ = url.Parse("/") 41 | da.clients["Vb9BP/h81n3GpTTB"] = &digest_client{nc: 0, last_seen: time.Now().UnixNano()} 42 | if u, _ := da.CheckAuth(r); u != "" { 43 | t.Fatal("non-empty auth for bad request path") 44 | } 45 | 46 | r.URL, _ = url.Parse("/t3") 47 | da.clients["Vb9BP/h81n3GpTTB"] = &digest_client{nc: 0, last_seen: time.Now().UnixNano()} 48 | if u, _ := da.CheckAuth(r); u != "" { 49 | t.Fatal("non-empty auth for bad request path") 50 | } 51 | 52 | da.clients["+RbVXSbIoa1SaJk1"] = &digest_client{nc: 0, last_seen: time.Now().UnixNano()} 53 | r.Header.Set("Authorization", `Digest username="test", realm="example.com", nonce="+RbVXSbIoa1SaJk1", uri="/", cnonce="NjE4NDkw", nc=00000001, qop="auth", response="c08918024d7faaabd5424654c4e3ad1c", opaque="U7H+ier3Ae8Skd/g", algorithm="MD5"`) 54 | if u, _ := da.CheckAuth(r); u != "test" { 55 | t.Fatal("empty auth for valid request in subpath") 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /golibs/src/github.com/abbot/go-http-auth/examples/basic.go: -------------------------------------------------------------------------------- 1 | /* 2 | Example application using Basic auth 3 | 4 | Build with: 5 | 6 | go build basic.go 7 | */ 8 | 9 | package main 10 | 11 | import ( 12 | auth ".." 13 | "fmt" 14 | "net/http" 15 | ) 16 | 17 | func Secret(user, realm string) string { 18 | if user == "john" { 19 | // password is "hello" 20 | return "$1$dlPL2MqE$oQmn16q49SqdmhenQuNgs1" 21 | } 22 | return "" 23 | } 24 | 25 | func handle(w http.ResponseWriter, r *auth.AuthenticatedRequest) { 26 | fmt.Fprintf(w, "

Hello, %s!

", r.Username) 27 | } 28 | 29 | func main() { 30 | authenticator := auth.NewBasicAuthenticator("example.com", Secret) 31 | http.HandleFunc("/", authenticator.Wrap(handle)) 32 | http.ListenAndServe(":8080", nil) 33 | } 34 | -------------------------------------------------------------------------------- /golibs/src/github.com/abbot/go-http-auth/examples/digest.go: -------------------------------------------------------------------------------- 1 | /* 2 | Example application using Digest auth 3 | 4 | Build with: 5 | 6 | go build digest.go 7 | */ 8 | 9 | package main 10 | 11 | import ( 12 | auth ".." 13 | "fmt" 14 | "net/http" 15 | ) 16 | 17 | func Secret(user, realm string) string { 18 | if user == "john" { 19 | // password is "hello" 20 | return "b98e16cbc3d01734b264adba7baa3bf9" 21 | } 22 | return "" 23 | } 24 | 25 | func handle(w http.ResponseWriter, r *auth.AuthenticatedRequest) { 26 | fmt.Fprintf(w, "

Hello, %s!

", r.Username) 27 | } 28 | 29 | func main() { 30 | authenticator := auth.NewDigestAuthenticator("example.com", Secret) 31 | http.HandleFunc("/", authenticator.Wrap(handle)) 32 | http.ListenAndServe(":8080", nil) 33 | } 34 | -------------------------------------------------------------------------------- /golibs/src/github.com/abbot/go-http-auth/examples/wrapped.go: -------------------------------------------------------------------------------- 1 | /* 2 | Example demonstrating how to wrap an application which is unaware of 3 | authenticated requests with a "pass-through" authentication 4 | 5 | Build with: 6 | 7 | go build wrapped.go 8 | */ 9 | 10 | package main 11 | 12 | import ( 13 | auth ".." 14 | "fmt" 15 | "net/http" 16 | ) 17 | 18 | func Secret(user, realm string) string { 19 | if user == "john" { 20 | // password is "hello" 21 | return "$1$dlPL2MqE$oQmn16q49SqdmhenQuNgs1" 22 | } 23 | return "" 24 | } 25 | 26 | func regular_handler(w http.ResponseWriter, r *http.Request) { 27 | fmt.Fprintf(w, "

This application is unaware of authentication

") 28 | } 29 | 30 | func main() { 31 | authenticator := auth.NewBasicAuthenticator("example.com", Secret) 32 | http.HandleFunc("/", auth.JustCheck(authenticator, regular_handler)) 33 | http.ListenAndServe(":8080", nil) 34 | } 35 | -------------------------------------------------------------------------------- /golibs/src/github.com/abbot/go-http-auth/md5crypt.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import "crypto/md5" 4 | import "strings" 5 | 6 | const itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" 7 | 8 | var md5_crypt_swaps = [16]int{12, 6, 0, 13, 7, 1, 14, 8, 2, 15, 9, 3, 5, 10, 4, 11} 9 | 10 | type MD5Entry struct { 11 | Magic, Salt, Hash []byte 12 | } 13 | 14 | func NewMD5Entry(e string) *MD5Entry { 15 | parts := strings.SplitN(e, "$", 4) 16 | if len(parts) != 4 { 17 | return nil 18 | } 19 | return &MD5Entry{ 20 | Magic: []byte("$" + parts[1] + "$"), 21 | Salt: []byte(parts[2]), 22 | Hash: []byte(parts[3]), 23 | } 24 | } 25 | 26 | /* 27 | MD5 password crypt implementation 28 | */ 29 | func MD5Crypt(password, salt, magic []byte) []byte { 30 | d := md5.New() 31 | 32 | d.Write(password) 33 | d.Write(magic) 34 | d.Write(salt) 35 | 36 | d2 := md5.New() 37 | d2.Write(password) 38 | d2.Write(salt) 39 | d2.Write(password) 40 | 41 | for i, mixin := 0, d2.Sum(nil); i < len(password); i++ { 42 | d.Write([]byte{mixin[i%16]}) 43 | } 44 | 45 | for i := len(password); i != 0; i >>= 1 { 46 | if i&1 == 0 { 47 | d.Write([]byte{password[0]}) 48 | } else { 49 | d.Write([]byte{0}) 50 | } 51 | } 52 | 53 | final := d.Sum(nil) 54 | 55 | for i := 0; i < 1000; i++ { 56 | d2 := md5.New() 57 | if i&1 == 0 { 58 | d2.Write(final) 59 | } else { 60 | d2.Write(password) 61 | } 62 | 63 | if i%3 != 0 { 64 | d2.Write(salt) 65 | } 66 | 67 | if i%7 != 0 { 68 | d2.Write(password) 69 | } 70 | 71 | if i&1 == 0 { 72 | d2.Write(password) 73 | } else { 74 | d2.Write(final) 75 | } 76 | final = d2.Sum(nil) 77 | } 78 | 79 | result := make([]byte, 0, 22) 80 | v := uint(0) 81 | bits := uint(0) 82 | for _, i := range md5_crypt_swaps { 83 | v |= (uint(final[i]) << bits) 84 | for bits = bits + 8; bits > 6; bits -= 6 { 85 | result = append(result, itoa64[v&0x3f]) 86 | v >>= 6 87 | } 88 | } 89 | result = append(result, itoa64[v&0x3f]) 90 | 91 | return append(append(append(magic, salt...), '$'), result...) 92 | } 93 | -------------------------------------------------------------------------------- /golibs/src/github.com/abbot/go-http-auth/md5crypt_test.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import "testing" 4 | 5 | func Test_MD5Crypt(t *testing.T) { 6 | test_cases := [][]string{ 7 | {"apache", "$apr1$J.w5a/..$IW9y6DR0oO/ADuhlMF5/X1"}, 8 | {"pass", "$1$YeNsbWdH$wvOF8JdqsoiLix754LTW90"}, 9 | } 10 | for _, tc := range test_cases { 11 | e := NewMD5Entry(tc[1]) 12 | result := MD5Crypt([]byte(tc[0]), e.Salt, e.Magic) 13 | if string(result) != tc[1] { 14 | t.Fatalf("MD5Crypt returned '%s' instead of '%s'", string(result), tc[1]) 15 | } 16 | t.Logf("MD5Crypt: '%s' (%s%s$) -> %s", tc[0], e.Magic, e.Salt, result) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /golibs/src/github.com/abbot/go-http-auth/misc.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import "encoding/base64" 4 | import "crypto/md5" 5 | import "crypto/rand" 6 | import "fmt" 7 | 8 | /* 9 | Return a random 16-byte base64 alphabet string 10 | */ 11 | func RandomKey() string { 12 | k := make([]byte, 12) 13 | for bytes := 0; bytes < len(k); { 14 | n, err := rand.Read(k[bytes:]) 15 | if err != nil { 16 | panic("rand.Read() failed") 17 | } 18 | bytes += n 19 | } 20 | return base64.StdEncoding.EncodeToString(k) 21 | } 22 | 23 | /* 24 | H function for MD5 algorithm (returns a lower-case hex MD5 digest) 25 | */ 26 | func H(data string) string { 27 | digest := md5.New() 28 | digest.Write([]byte(data)) 29 | return fmt.Sprintf("%x", digest.Sum(nil)) 30 | } 31 | -------------------------------------------------------------------------------- /golibs/src/github.com/abbot/go-http-auth/misc_test.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import "testing" 4 | 5 | func TestH(t *testing.T) { 6 | const hello = "Hello, world!" 7 | const hello_md5 = "6cd3556deb0da54bca060b4c39479839" 8 | h := H(hello) 9 | if h != hello_md5 { 10 | t.Fatal("Incorrect digest for test string:", h, "instead of", hello_md5) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /golibs/src/github.com/abbot/go-http-auth/test.htdigest: -------------------------------------------------------------------------------- 1 | test:example.com:aa78524fceb0e50fd8ca96dd818b8cf9 2 | -------------------------------------------------------------------------------- /golibs/src/github.com/abbot/go-http-auth/test.htpasswd: -------------------------------------------------------------------------------- 1 | test:{SHA}qvTGHdzF6KLavt4PO0gs2a6pQ00= 2 | test2:$apr1$a0j62R97$mYqFkloXH0/UOaUnAiV2b0 3 | -------------------------------------------------------------------------------- /golibs/src/github.com/abbot/go-http-auth/users.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import "encoding/csv" 4 | import "os" 5 | 6 | /* 7 | SecretProvider is used by authenticators. Takes user name and realm 8 | as an argument, returns secret required for authentication (HA1 for 9 | digest authentication, properly encrypted password for basic). 10 | */ 11 | type SecretProvider func(user, realm string) string 12 | 13 | /* 14 | Common functions for file auto-reloading 15 | */ 16 | type File struct { 17 | Path string 18 | Info os.FileInfo 19 | /* must be set in inherited types during initialization */ 20 | Reload func() 21 | } 22 | 23 | func (f *File) ReloadIfNeeded() { 24 | info, err := os.Stat(f.Path) 25 | if err != nil { 26 | panic(err) 27 | } 28 | if f.Info == nil || f.Info.ModTime() != info.ModTime() { 29 | f.Info = info 30 | f.Reload() 31 | } 32 | } 33 | 34 | /* 35 | Structure used for htdigest file authentication. Users map realms to 36 | maps of users to their HA1 digests. 37 | */ 38 | type HtdigestFile struct { 39 | File 40 | Users map[string]map[string]string 41 | } 42 | 43 | func reload_htdigest(hf *HtdigestFile) { 44 | r, err := os.Open(hf.Path) 45 | if err != nil { 46 | panic(err) 47 | } 48 | csv_reader := csv.NewReader(r) 49 | csv_reader.Comma = ':' 50 | csv_reader.Comment = '#' 51 | csv_reader.TrimLeadingSpace = true 52 | 53 | records, err := csv_reader.ReadAll() 54 | if err != nil { 55 | panic(err) 56 | } 57 | 58 | hf.Users = make(map[string]map[string]string) 59 | for _, record := range records { 60 | _, exists := hf.Users[record[1]] 61 | if !exists { 62 | hf.Users[record[1]] = make(map[string]string) 63 | } 64 | hf.Users[record[1]][record[0]] = record[2] 65 | } 66 | } 67 | 68 | /* 69 | SecretProvider implementation based on htdigest-formated files. Will 70 | reload htdigest file on changes. Will panic on syntax errors in 71 | htdigest files. 72 | */ 73 | func HtdigestFileProvider(filename string) SecretProvider { 74 | hf := &HtdigestFile{File: File{Path: filename}} 75 | hf.Reload = func() { reload_htdigest(hf) } 76 | return func(user, realm string) string { 77 | hf.ReloadIfNeeded() 78 | _, exists := hf.Users[realm] 79 | if !exists { 80 | return "" 81 | } 82 | digest, exists := hf.Users[realm][user] 83 | if !exists { 84 | return "" 85 | } 86 | return digest 87 | } 88 | } 89 | 90 | /* 91 | Structure used for htdigest file authentication. Users map users to 92 | their salted encrypted password 93 | */ 94 | type HtpasswdFile struct { 95 | File 96 | Users map[string]string 97 | } 98 | 99 | func reload_htpasswd(h *HtpasswdFile) { 100 | r, err := os.Open(h.Path) 101 | if err != nil { 102 | panic(err) 103 | } 104 | csv_reader := csv.NewReader(r) 105 | csv_reader.Comma = ':' 106 | csv_reader.Comment = '#' 107 | csv_reader.TrimLeadingSpace = true 108 | 109 | records, err := csv_reader.ReadAll() 110 | if err != nil { 111 | panic(err) 112 | } 113 | 114 | h.Users = make(map[string]string) 115 | for _, record := range records { 116 | h.Users[record[0]] = record[1] 117 | } 118 | } 119 | 120 | /* 121 | SecretProvider implementation based on htpasswd-formated files. Will 122 | reload htpasswd file on changes. Will panic on syntax errors in 123 | htpasswd files. Realm argument of the SecretProvider is ignored. 124 | */ 125 | func HtpasswdFileProvider(filename string) SecretProvider { 126 | h := &HtpasswdFile{File: File{Path: filename}} 127 | h.Reload = func() { reload_htpasswd(h) } 128 | return func(user, realm string) string { 129 | h.ReloadIfNeeded() 130 | password, exists := h.Users[user] 131 | if !exists { 132 | return "" 133 | } 134 | return password 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /golibs/src/github.com/abbot/go-http-auth/users_test.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestHtdigestFile(t *testing.T) { 8 | secrets := HtdigestFileProvider("test.htdigest") 9 | digest := secrets("test", "example.com") 10 | if digest != "aa78524fceb0e50fd8ca96dd818b8cf9" { 11 | t.Fatal("Incorrect digest for test user:", digest) 12 | } 13 | digest = secrets("test", "example1.com") 14 | if digest != "" { 15 | t.Fatal("Got digest for user in non-existant realm:", digest) 16 | } 17 | digest = secrets("test1", "example.com") 18 | if digest != "" { 19 | t.Fatal("Got digest for non-existant user:", digest) 20 | } 21 | } 22 | 23 | func TestHtpasswdFile(t *testing.T) { 24 | secrets := HtpasswdFileProvider("test.htpasswd") 25 | passwd := secrets("test", "blah") 26 | if passwd != "{SHA}qvTGHdzF6KLavt4PO0gs2a6pQ00=" { 27 | t.Fatal("Incorrect passwd for test user:", passwd) 28 | } 29 | passwd = secrets("test3", "blah") 30 | if passwd != "" { 31 | t.Fatal("Got passwd for non-existant user:", passwd) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /golibs/src/github.com/damicon/zfswatcher: -------------------------------------------------------------------------------- 1 | ../../../.. -------------------------------------------------------------------------------- /golibs/src/github.com/ogier/pflag/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Alex Ogier. All rights reserved. 2 | Copyright (c) 2012 The Go Authors. All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above 11 | copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the 13 | distribution. 14 | * Neither the name of Google Inc. nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /golibs/src/github.com/ogier/pflag/README.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | pflag is a drop-in replacement for Go's flag package, implementing 4 | POSIX/GNU-style --flags. 5 | 6 | pflag is compatible with the [GNU extensions to the POSIX recommendations 7 | for command-line options][1]. For a more precise description, see the 8 | "Command-line flag syntax" section below. 9 | 10 | [1]: http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html 11 | 12 | pflag is available under the same style of BSD license as the Go language, 13 | which can be found in the LICENSE file. 14 | 15 | ## Installation 16 | 17 | pflag is available using the standard `go get` command. 18 | 19 | Install by running: 20 | 21 | go get github.com/ogier/pflag 22 | 23 | Run tests by running: 24 | 25 | go test github.com/ogier/pflag 26 | 27 | ## Usage 28 | 29 | pflag is a drop-in replacement of Go's native flag package. If you import 30 | pflag under the name "flag" then all code should continue to function 31 | with no changes. 32 | 33 | ``` go 34 | import flag "github.com/ogier/pflag" 35 | ``` 36 | 37 | There is one exception to this: if you directly instantiate the Flag struct 38 | there is one more field "Shorthand" that you will need to set. 39 | Most code never instantiates this struct directly, and instead uses 40 | functions such as String(), BoolVar(), and Var(), and is therefore 41 | unaffected. 42 | 43 | Define flags using flag.String(), Bool(), Int(), etc. 44 | 45 | This declares an integer flag, -flagname, stored in the pointer ip, with type *int. 46 | 47 | ``` go 48 | var ip *int = flag.Int("flagname", 1234, "help message for flagname") 49 | ``` 50 | 51 | If you like, you can bind the flag to a variable using the Var() functions. 52 | 53 | ``` go 54 | var flagvar int 55 | func init() { 56 | flag.IntVar(&flagvar, "flagname", 1234, "help message for flagname") 57 | } 58 | ``` 59 | 60 | Or you can create custom flags that satisfy the Value interface (with 61 | pointer receivers) and couple them to flag parsing by 62 | 63 | ``` go 64 | flag.Var(&flagVal, "name", "help message for flagname") 65 | ``` 66 | 67 | For such flags, the default value is just the initial value of the variable. 68 | 69 | After all flags are defined, call 70 | 71 | ``` go 72 | flag.Parse() 73 | ``` 74 | 75 | to parse the command line into the defined flags. 76 | 77 | Flags may then be used directly. If you're using the flags themselves, 78 | they are all pointers; if you bind to variables, they're values. 79 | 80 | ``` go 81 | fmt.Println("ip has value ", *ip) 82 | fmt.Println("flagvar has value ", flagvar) 83 | ``` 84 | 85 | After parsing, the arguments after the flag are available as the 86 | slice flag.Args() or individually as flag.Arg(i). 87 | The arguments are indexed from 0 through flag.NArg()-1. 88 | 89 | The pflag package also defines some new functions that are not in flag, 90 | that give one-letter shorthands for flags. You can use these by appending 91 | 'P' to the name of any function that defines a flag. 92 | 93 | ``` go 94 | var ip = flag.IntP("flagname", "f", 1234, "help message") 95 | var flagvar bool 96 | func init() { 97 | flag.BoolVarP("boolname", "b", true, "help message") 98 | } 99 | flag.VarP(&flagVar, "varname", "v", 1234, "help message") 100 | ``` 101 | 102 | Shorthand letters can be used with single dashes on the command line. 103 | Boolean shorthand flags can be combined with other shorthand flags. 104 | 105 | The default set of command-line flags is controlled by 106 | top-level functions. The FlagSet type allows one to define 107 | independent sets of flags, such as to implement subcommands 108 | in a command-line interface. The methods of FlagSet are 109 | analogous to the top-level functions for the command-line 110 | flag set. 111 | 112 | ## Command line flag syntax 113 | 114 | ``` 115 | --flag // boolean flags only 116 | --flag=x 117 | ``` 118 | 119 | Unlike the flag package, a single dash before an option means something 120 | different than a double dash. Single dashes signify a series of shorthand 121 | letters for flags. All but the last shorthand letter must be boolean flags. 122 | 123 | ``` 124 | // boolean flags 125 | -f 126 | -abc 127 | 128 | // non-boolean flags 129 | -n 1234 130 | -Ifile 131 | 132 | // mixed 133 | -abcs "hello" 134 | -abcn1234 135 | ``` 136 | 137 | Flag parsing stops after the terminator "--". Unlike the flag package, 138 | flags can be interspersed with arguments anywhere on the command line 139 | before this terminator. 140 | 141 | Integer flags accept 1234, 0664, 0x1234 and may be negative. 142 | Boolean flags (in their long form) accept 1, 0, t, f, true, false, 143 | TRUE, FALSE, True, False. 144 | Duration flags accept any input valid for time.ParseDuration. 145 | 146 | ## More info 147 | 148 | You can get a full reference of the pflag package through go's standard 149 | documentation system, for example by running `godoc -http=:6060` and 150 | browsing to [http://localhost:6060/pkg/github.com/ogier/pflag][2] after 151 | installation. 152 | 153 | [2]: http://localhost:6060/pkg/github.com/ogier/pflag 154 | -------------------------------------------------------------------------------- /golibs/src/github.com/ogier/pflag/example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // These examples demonstrate more intricate uses of the flag package. 6 | package pflag_test 7 | 8 | import ( 9 | "errors" 10 | "fmt" 11 | "strings" 12 | "time" 13 | 14 | flag "github.com/ogier/pflag" 15 | ) 16 | 17 | // Example 1: A single string flag called "species" with default value "gopher". 18 | var species = flag.String("species", "gopher", "the species we are studying") 19 | 20 | // Example 2: A flag with a shorthand letter. 21 | var gopherType = flag.StringP("gopher_type", "g", "pocket", "the variety of gopher") 22 | 23 | // Example 3: A user-defined flag type, a slice of durations. 24 | type interval []time.Duration 25 | 26 | // String is the method to format the flag's value, part of the flag.Value interface. 27 | // The String method's output will be used in diagnostics. 28 | func (i *interval) String() string { 29 | return fmt.Sprint(*i) 30 | } 31 | 32 | // Set is the method to set the flag value, part of the flag.Value interface. 33 | // Set's argument is a string to be parsed to set the flag. 34 | // It's a comma-separated list, so we split it. 35 | func (i *interval) Set(value string) error { 36 | // If we wanted to allow the flag to be set multiple times, 37 | // accumulating values, we would delete this if statement. 38 | // That would permit usages such as 39 | // -deltaT 10s -deltaT 15s 40 | // and other combinations. 41 | if len(*i) > 0 { 42 | return errors.New("interval flag already set") 43 | } 44 | for _, dt := range strings.Split(value, ",") { 45 | duration, err := time.ParseDuration(dt) 46 | if err != nil { 47 | return err 48 | } 49 | *i = append(*i, duration) 50 | } 51 | return nil 52 | } 53 | 54 | // Define a flag to accumulate durations. Because it has a special type, 55 | // we need to use the Var function and therefore create the flag during 56 | // init. 57 | 58 | var intervalFlag interval 59 | 60 | func init() { 61 | // Tie the command-line flag to the intervalFlag variable and 62 | // set a usage message. 63 | flag.Var(&intervalFlag, "deltaT", "comma-separated list of intervals to use between events") 64 | } 65 | 66 | func Example() { 67 | // All the interesting pieces are with the variables declared above, but 68 | // to enable the flag package to see the flags defined there, one must 69 | // execute, typically at the start of main (not init!): 70 | // flag.Parse() 71 | // We don't run it here because this is not a main function and 72 | // the testing suite has already parsed the flags. 73 | } 74 | -------------------------------------------------------------------------------- /golibs/src/github.com/ogier/pflag/export_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package pflag 6 | 7 | import ( 8 | "io/ioutil" 9 | "os" 10 | ) 11 | 12 | // Additional routines compiled into the package only during testing. 13 | 14 | // ResetForTesting clears all flag state and sets the usage function as directed. 15 | // After calling ResetForTesting, parse errors in flag handling will not 16 | // exit the program. 17 | func ResetForTesting(usage func()) { 18 | commandLine = &FlagSet{ 19 | name: os.Args[0], 20 | errorHandling: ContinueOnError, 21 | output: ioutil.Discard, 22 | } 23 | Usage = usage 24 | } 25 | 26 | // CommandLine returns the default FlagSet. 27 | func CommandLine() *FlagSet { 28 | return commandLine 29 | } 30 | -------------------------------------------------------------------------------- /golibs/src/github.com/snabb/smtp/README.md: -------------------------------------------------------------------------------- 1 | smtp 2 | ==== 3 | 4 | Go standard net/smtp module with some tweaks. 5 | -------------------------------------------------------------------------------- /golibs/src/github.com/snabb/smtp/auth.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package smtp 6 | 7 | import ( 8 | "crypto/hmac" 9 | "crypto/md5" 10 | "errors" 11 | "fmt" 12 | ) 13 | 14 | // Auth is implemented by an SMTP authentication mechanism. 15 | type Auth interface { 16 | // Start begins an authentication with a server. 17 | // It returns the name of the authentication protocol 18 | // and optionally data to include in the initial AUTH message 19 | // sent to the server. It can return proto == "" to indicate 20 | // that the authentication should be skipped. 21 | // If it returns a non-nil error, the SMTP client aborts 22 | // the authentication attempt and closes the connection. 23 | Start(server *ServerInfo) (proto string, toServer []byte, err error) 24 | 25 | // Next continues the authentication. The server has just sent 26 | // the fromServer data. If more is true, the server expects a 27 | // response, which Next should return as toServer; otherwise 28 | // Next should return toServer == nil. 29 | // If Next returns a non-nil error, the SMTP client aborts 30 | // the authentication attempt and closes the connection. 31 | Next(fromServer []byte, more bool) (toServer []byte, err error) 32 | } 33 | 34 | // ServerInfo records information about an SMTP server. 35 | type ServerInfo struct { 36 | Name string // SMTP server name 37 | TLS bool // using TLS, with valid certificate for Name 38 | Auth []string // advertised authentication mechanisms 39 | } 40 | 41 | type plainAuth struct { 42 | identity, username, password string 43 | host string 44 | } 45 | 46 | // PlainAuth returns an Auth that implements the PLAIN authentication 47 | // mechanism as defined in RFC 4616. 48 | // The returned Auth uses the given username and password to authenticate 49 | // on TLS connections to host and act as identity. Usually identity will be 50 | // left blank to act as username. 51 | func PlainAuth(identity, username, password, host string) Auth { 52 | return &plainAuth{identity, username, password, host} 53 | } 54 | 55 | func (a *plainAuth) Start(server *ServerInfo) (string, []byte, error) { 56 | if !server.TLS { 57 | return "", nil, errors.New("unencrypted connection") 58 | } 59 | if server.Name != a.host { 60 | return "", nil, errors.New("wrong host name") 61 | } 62 | resp := []byte(a.identity + "\x00" + a.username + "\x00" + a.password) 63 | return "PLAIN", resp, nil 64 | } 65 | 66 | func (a *plainAuth) Next(fromServer []byte, more bool) ([]byte, error) { 67 | if more { 68 | // We've already sent everything. 69 | return nil, errors.New("unexpected server challenge") 70 | } 71 | return nil, nil 72 | } 73 | 74 | type cramMD5Auth struct { 75 | username, secret string 76 | } 77 | 78 | // CRAMMD5Auth returns an Auth that implements the CRAM-MD5 authentication 79 | // mechanism as defined in RFC 2195. 80 | // The returned Auth uses the given username and secret to authenticate 81 | // to the server using the challenge-response mechanism. 82 | func CRAMMD5Auth(username, secret string) Auth { 83 | return &cramMD5Auth{username, secret} 84 | } 85 | 86 | func (a *cramMD5Auth) Start(server *ServerInfo) (string, []byte, error) { 87 | return "CRAM-MD5", nil, nil 88 | } 89 | 90 | func (a *cramMD5Auth) Next(fromServer []byte, more bool) ([]byte, error) { 91 | if more { 92 | d := hmac.New(md5.New, []byte(a.secret)) 93 | d.Write(fromServer) 94 | s := make([]byte, 0, d.Size()) 95 | return []byte(fmt.Sprintf("%s %x", a.username, d.Sum(s))), nil 96 | } 97 | return nil, nil 98 | } 99 | -------------------------------------------------------------------------------- /leds.go: -------------------------------------------------------------------------------- 1 | // 2 | // leds.go 3 | // 4 | // Copyright © 2012-2013 Damicon Kraa Oy 5 | // 6 | // This file is part of zfswatcher. 7 | // 8 | // Zfswatcher is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // Zfswatcher is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with zfswatcher. If not, see . 20 | // 21 | 22 | package main 23 | 24 | import ( 25 | "errors" 26 | "fmt" 27 | "github.com/damicon/zfswatcher/notifier" 28 | "os" 29 | "os/exec" 30 | "strings" 31 | "sync" 32 | ) 33 | 34 | // List of devices, paths and current LED statuses. 35 | 36 | type ibpiID int32 37 | 38 | const ( 39 | IBPI_UNKNOWN ibpiID = iota 40 | IBPI_NORMAL 41 | IBPI_LOCATE 42 | IBPI_LOCATE_OFF 43 | IBPI_FAIL 44 | IBPI_REBUILD_P 45 | IBPI_REBUILD_S 46 | IBPI_PFA 47 | IBPI_HOTSPARE 48 | IBPI_CRITICAL_ARRAY 49 | IBPI_FAILED_ARRAY 50 | IBPI_UNDEFINED 51 | ) 52 | 53 | var ibpiNameToId = map[string]ibpiID{ 54 | "normal": IBPI_NORMAL, 55 | "locate": IBPI_LOCATE, 56 | "fail": IBPI_FAIL, 57 | "rebuild": IBPI_REBUILD_P, 58 | "rebuild_p": IBPI_REBUILD_P, 59 | "rebuild_s": IBPI_REBUILD_S, 60 | "pfa": IBPI_PFA, 61 | "hotspare": IBPI_HOTSPARE, 62 | "critical_array": IBPI_CRITICAL_ARRAY, 63 | "failed_array": IBPI_FAILED_ARRAY, 64 | "undefined": IBPI_UNDEFINED, 65 | } 66 | 67 | var ibpiToLedCtl = map[ibpiID]string{ 68 | IBPI_NORMAL: "normal", 69 | IBPI_LOCATE: "locate", // paalle kun disk offline? 70 | IBPI_LOCATE_OFF: "locate_off", 71 | IBPI_FAIL: "failure", 72 | IBPI_REBUILD_P: "rebuild_p", 73 | IBPI_REBUILD_S: "rebuild", 74 | IBPI_PFA: "pfa", // ei toimi 75 | IBPI_HOTSPARE: "hotspare", 76 | IBPI_CRITICAL_ARRAY: "degraded", // ei toimi 77 | IBPI_FAILED_ARRAY: "failed_array", // ei toimi 78 | } 79 | 80 | type devLed struct { 81 | name string 82 | path string 83 | state ibpiID 84 | locate bool 85 | } 86 | 87 | var ( 88 | devLeds map[string]*devLed 89 | devLedsMutex sync.Mutex 90 | ) 91 | 92 | type devStateToIbpiMap map[string]ibpiID 93 | 94 | // Implement fmt.Scanner interface. 95 | func (simapp *devStateToIbpiMap) Scan(state fmt.ScanState, verb rune) error { 96 | smap := stringToStringMap{} 97 | err := smap.Scan(state, verb) 98 | if err != nil { 99 | return err 100 | } 101 | simap := make(devStateToIbpiMap) 102 | for a, b := range smap { 103 | var id ibpiID 104 | if n, err := fmt.Sscan(b, &id); n != 1 { 105 | return err 106 | } 107 | simap[a] = id 108 | } 109 | *simapp = simap 110 | return nil 111 | } 112 | 113 | // Implement fmt.Scanner interface. 114 | func (i *ibpiID) Scan(state fmt.ScanState, verb rune) error { 115 | ibpistr, err := state.Token(false, func(r rune) bool { return true }) 116 | if err != nil { 117 | return err 118 | } 119 | ibpiid, ok := ibpiNameToId[string(ibpistr)] 120 | if !ok { 121 | return errors.New(`invalid IBPI string "` + string(ibpistr) + `"`) 122 | } 123 | *i = ibpiid 124 | return nil 125 | } 126 | 127 | func (simap devStateToIbpiMap) getIbpiId(str string) ibpiID { 128 | id, ok := simap[str] 129 | if !ok { 130 | return IBPI_NORMAL 131 | } 132 | return id 133 | } 134 | 135 | func setDevLeds(devandled map[string]ibpiID) { 136 | var cmds []string 137 | 138 | devLedsMutex.Lock() 139 | // set the new led state in our internal array 140 | for dev, led := range devandled { 141 | if err := ensureDevLeds(dev); err != nil { 142 | notify.Print(notifier.ERR, "failed setting LED: ", err) 143 | continue 144 | } 145 | devLeds[dev].state = led 146 | } 147 | // make ledctl command line based on the status array 148 | // note: must update all leds as otherwise ledctl turns the leds off! 149 | for dev, devled := range devLeds { 150 | // skip devices which are currently missing 151 | st, err := os.Stat(devled.path) 152 | if err != nil || st.Mode()&os.ModeDevice == 0 { 153 | notify.Print(notifier.DEBUG, "skipping missing device LED: ", dev) 154 | continue 155 | } 156 | // reset unknown status to normal 157 | if devled.state == IBPI_UNKNOWN { 158 | devled.state = IBPI_NORMAL 159 | } 160 | // locate overrides other status 161 | if devled.locate { 162 | cmds = append(cmds, ibpiToLedCtl[IBPI_LOCATE]+"="+devled.path) 163 | } else { 164 | cmds = append(cmds, ibpiToLedCtl[devled.state]+"="+devled.path) 165 | } 166 | } 167 | devLedsMutex.Unlock() 168 | 169 | if len(cmds) > 0 { 170 | // notify.Print(notifier.DEBUG, `running: `, cfg.Leds.Ledctlcmd+" "+strings.Join(cmds, " ")) 171 | cmd := exec.Command(cfg.Leds.Ledctlcmd, cmds...) 172 | cmd.Stdout, cmd.Stderr = nil, nil // suppress useless output 173 | err := cmd.Run() 174 | if err != nil { 175 | notify.Print(notifier.ERR, `running "`, 176 | cfg.Leds.Ledctlcmd+" "+strings.Join(cmds, " "), 177 | `" failed: `, err) 178 | } 179 | } 180 | } 181 | 182 | func ensureDevLeds(dev string) error { 183 | // must be called when devLedsMutex is locked! 184 | 185 | if devLeds == nil { 186 | devLeds = make(map[string]*devLed, 0) 187 | } 188 | _, ok := devLeds[dev] 189 | if ok { 190 | return nil 191 | } 192 | path, err := findDevicePath(dev) 193 | _ = err 194 | if err != nil { 195 | return err 196 | } 197 | devLeds[dev] = &devLed{name: dev, path: path} 198 | 199 | return nil 200 | } 201 | 202 | func locateOn(dev string) error { 203 | devLedsMutex.Lock() 204 | 205 | if err := ensureDevLeds(dev); err != nil { 206 | devLedsMutex.Unlock() 207 | return err 208 | } 209 | devLeds[dev].locate = true 210 | 211 | devLedsMutex.Unlock() 212 | 213 | setDevLeds(nil) 214 | 215 | return nil 216 | } 217 | 218 | func locateOff(dev string) error { 219 | devLedsMutex.Lock() 220 | 221 | if err := ensureDevLeds(dev); err != nil { 222 | devLedsMutex.Unlock() 223 | return err 224 | } 225 | devLeds[dev].locate = false 226 | 227 | devLedsMutex.Unlock() 228 | 229 | setDevLeds(nil) 230 | 231 | return nil 232 | } 233 | 234 | func locateQuery(dev string) (bool, error) { 235 | devLedsMutex.Lock() 236 | defer devLedsMutex.Unlock() 237 | 238 | if err := ensureDevLeds(dev); err != nil { 239 | return false, err 240 | } 241 | return devLeds[dev].locate, nil 242 | } 243 | 244 | // eof 245 | -------------------------------------------------------------------------------- /notifier/facility.go: -------------------------------------------------------------------------------- 1 | // 2 | // facility.go 3 | // 4 | // Copyright © 2012-2013 Damicon Kraa Oy 5 | // 6 | // This file is part of zfswatcher. 7 | // 8 | // Zfswatcher is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // Zfswatcher is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with zfswatcher. If not, see . 20 | // 21 | 22 | package notifier 23 | 24 | import ( 25 | "errors" 26 | "fmt" 27 | ) 28 | 29 | // Syslog message facility codes. 30 | type SyslogFacility uint32 31 | 32 | const ( 33 | syslog_FACILITY_MIN SyslogFacility = 0 34 | syslog_FACILITY_KERN SyslogFacility = 0 35 | syslog_FACILITY_USER SyslogFacility = 1 36 | syslog_FACILITY_MAIL SyslogFacility = 2 37 | syslog_FACILITY_DAEMON SyslogFacility = 3 38 | syslog_FACILITY_AUTH SyslogFacility = 4 39 | syslog_FACILITY_SYSLOG SyslogFacility = 5 40 | syslog_FACILITY_LPR SyslogFacility = 6 41 | syslog_FACILITY_NEWS SyslogFacility = 7 42 | syslog_FACILITY_UUCP SyslogFacility = 8 43 | syslog_FACILITY_CRON SyslogFacility = 9 44 | syslog_FACILITY_AUTHPRIV SyslogFacility = 10 45 | syslog_FACILITY_FTP SyslogFacility = 11 46 | syslog_FACILITY_LOCAL0 SyslogFacility = 16 47 | syslog_FACILITY_LOCAL1 SyslogFacility = 17 48 | syslog_FACILITY_LOCAL2 SyslogFacility = 18 49 | syslog_FACILITY_LOCAL3 SyslogFacility = 19 50 | syslog_FACILITY_LOCAL4 SyslogFacility = 20 51 | syslog_FACILITY_LOCAL5 SyslogFacility = 21 52 | syslog_FACILITY_LOCAL6 SyslogFacility = 22 53 | syslog_FACILITY_LOCAL7 SyslogFacility = 23 54 | syslog_FACILITY_MAX SyslogFacility = 23 55 | ) 56 | 57 | var syslogFacilityCodes = map[string]SyslogFacility{ 58 | "kern": syslog_FACILITY_KERN, 59 | "user": syslog_FACILITY_USER, 60 | "mail": syslog_FACILITY_MAIL, 61 | "daemon": syslog_FACILITY_DAEMON, 62 | "auth": syslog_FACILITY_AUTH, 63 | "syslog": syslog_FACILITY_SYSLOG, 64 | "lpr": syslog_FACILITY_LPR, 65 | "news": syslog_FACILITY_NEWS, 66 | "uucp": syslog_FACILITY_UUCP, 67 | "cron": syslog_FACILITY_CRON, 68 | "authpriv": syslog_FACILITY_AUTHPRIV, 69 | "ftp": syslog_FACILITY_FTP, 70 | "local0": syslog_FACILITY_LOCAL0, 71 | "local1": syslog_FACILITY_LOCAL1, 72 | "local2": syslog_FACILITY_LOCAL2, 73 | "local3": syslog_FACILITY_LOCAL3, 74 | "local4": syslog_FACILITY_LOCAL4, 75 | "local5": syslog_FACILITY_LOCAL5, 76 | "local6": syslog_FACILITY_LOCAL6, 77 | "local7": syslog_FACILITY_LOCAL7, 78 | } 79 | 80 | // public API 81 | 82 | // Implement fmt.Scanner interface. This makes it possible to use fmt.Sscan*() 83 | // functions to parse syslog facility codes directly. Also "gcfg" package can 84 | // parse them in configuration files. 85 | func (f *SyslogFacility) Scan(state fmt.ScanState, verb rune) error { 86 | facstr, err := state.Token(false, func(r rune) bool { return true }) 87 | if err != nil { 88 | return err 89 | } 90 | fac, ok := syslogFacilityCodes[string(facstr)] 91 | if !ok { 92 | return errors.New(`invalid facility "` + string(facstr) + `"`) 93 | } 94 | *f = fac 95 | return nil 96 | } 97 | 98 | // eof 99 | -------------------------------------------------------------------------------- /notifier/logger_callback.go: -------------------------------------------------------------------------------- 1 | // 2 | // logger_callback.go 3 | // 4 | // Copyright © 2012-2013 Damicon Kraa Oy 5 | // 6 | // This file is part of zfswatcher. 7 | // 8 | // Zfswatcher is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // Zfswatcher is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with zfswatcher. If not, see . 20 | // 21 | 22 | package notifier 23 | 24 | func (n Notifier) loggerCallback(ch chan *Msg, f func(*Msg)) { 25 | defer n.wg.Done() 26 | for m := range ch { 27 | f(m) 28 | } 29 | } 30 | 31 | // AddLoggerCallback adds a log output which just calls back the given function 32 | // whenever there is a message to be logged. 33 | func (n *Notifier) AddLoggerCallback(s Severity, f func(*Msg)) error { 34 | ch := make(chan *Msg, chan_SIZE) 35 | n.wg.Add(1) 36 | go n.loggerCallback(ch, f) 37 | n.out = append(n.out, notifyOutput{severity: s, ch: ch, attachment: true}) 38 | return nil 39 | } 40 | 41 | // eof 42 | -------------------------------------------------------------------------------- /notifier/logger_file.go: -------------------------------------------------------------------------------- 1 | // 2 | // logger_file.go 3 | // 4 | // Copyright © 2012-2013 Damicon Kraa Oy 5 | // 6 | // This file is part of zfswatcher. 7 | // 8 | // Zfswatcher is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // Zfswatcher is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with zfswatcher. If not, see . 20 | // 21 | 22 | package notifier 23 | 24 | import ( 25 | "errors" 26 | "os" 27 | "strings" 28 | ) 29 | 30 | func (n *Notifier) loggerFile(ch chan *Msg, filename string) { 31 | defer n.wg.Done() 32 | 33 | var fileopen bool 34 | 35 | f, err := os.OpenFile(filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666) 36 | 37 | if err == nil { 38 | fileopen = true 39 | } else { 40 | checkInternalError("error opening log file", err) 41 | fileopen = false 42 | } 43 | 44 | for m := range ch { 45 | switch m.MsgType { 46 | case MSGTYPE_MESSAGE: 47 | if !fileopen { 48 | continue 49 | } 50 | _, err = f.WriteString(m.String() + "\n") 51 | checkInternalError("error writing log file", err) 52 | case MSGTYPE_ATTACHMENT: 53 | if !fileopen { 54 | continue 55 | } 56 | _, err = f.WriteString(">" + 57 | strings.Replace(strings.TrimRight(m.Text, "\n"), "\n", "\n>", -1) + 58 | "\n") 59 | checkInternalError("error writing log file", err) 60 | case MSGTYPE_REOPEN: 61 | if fileopen { 62 | err = f.Close() 63 | checkInternalError("error closing log file", err) 64 | } 65 | f, err = os.OpenFile(filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666) 66 | if err == nil { 67 | fileopen = true 68 | } else { 69 | checkInternalError("error re-opening log file", err) 70 | fileopen = false 71 | } 72 | } 73 | } 74 | if fileopen { 75 | err = f.Close() 76 | checkInternalError("error closing log file", err) 77 | } 78 | } 79 | 80 | // AddLoggerFile adds a file based logging output. 81 | func (n *Notifier) AddLoggerFile(s Severity, file string) error { 82 | switch { 83 | case s < severity_MIN || s > severity_MAX: 84 | return errors.New(`invalid "severity"`) 85 | case file == "": 86 | return errors.New(`"file" not defined`) 87 | } 88 | ch := make(chan *Msg, chan_SIZE) 89 | n.wg.Add(1) 90 | go n.loggerFile(ch, file) 91 | n.out = append(n.out, notifyOutput{severity: s, ch: ch, attachment: true}) 92 | return nil 93 | } 94 | 95 | // eof 96 | -------------------------------------------------------------------------------- /notifier/logger_smtp.go: -------------------------------------------------------------------------------- 1 | // 2 | // logger_smtp.go 3 | // 4 | // Copyright © 2012-2013 Damicon Kraa Oy 5 | // 6 | // This file is part of zfswatcher. 7 | // 8 | // Zfswatcher is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // Zfswatcher is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with zfswatcher. If not, see . 20 | // 21 | 22 | package notifier 23 | 24 | import ( 25 | "errors" 26 | "github.com/snabb/smtp" 27 | "net" 28 | "strings" 29 | "time" 30 | ) 31 | 32 | func (n *Notifier) sendEmailSMTP(server, username, password, from, to, subject, text string) { 33 | defer n.wg.Done() 34 | var auth smtp.Auth 35 | 36 | text = "From: " + from + "\r\n" + 37 | "To: " + strings.Join(strings.Fields(to), ", ") + "\r\n" + 38 | "Date: " + time.Now().Format(time.RFC1123Z) + "\r\n" + 39 | "Subject: " + subject + "\r\n" + 40 | "\r\n" + strings.Replace(text, "\n", "\r\n", -1) + "\r\n" 41 | 42 | if username != "" { 43 | host, _, err := net.SplitHostPort(server) 44 | checkInternalError("error parsing mail host:port", err) 45 | auth = smtp.PlainAuth("", username, password, host) 46 | } 47 | for retries := 0; true; retries++ { 48 | err := smtp.SendMail(server, auth, from, strings.Fields(to), []byte(text)) 49 | if err == nil { 50 | break 51 | } 52 | if retries < 3 { 53 | checkInternalError("error sending mail (retrying)", err) 54 | time.Sleep(retry_SLEEP * time.Millisecond) 55 | } else { 56 | checkInternalError("error sending mail (giving up)", err) 57 | break 58 | } 59 | } 60 | } 61 | 62 | func makeEmailText(mbuf, abuf []string) (text string) { 63 | if mbuf != nil { 64 | text += strings.Join(mbuf, "\n") 65 | text += "\n" 66 | mbuf = nil 67 | } 68 | if abuf != nil { 69 | text += strings.Join(abuf, "\n") 70 | text += "\n" 71 | abuf = nil 72 | } 73 | return text 74 | } 75 | 76 | func (n *Notifier) loggerEmailSMTP(ch chan *Msg, server, username, password, from, to, subject string, throttle time.Duration) { 77 | defer n.wg.Done() 78 | var mbuf []string 79 | var abuf []string 80 | var lastFlush time.Time 81 | var throttleTimer *time.Timer 82 | 83 | severityTrack := DEBUG 84 | 85 | for { 86 | var throttleC <-chan time.Time 87 | 88 | if throttleTimer != nil { 89 | throttleC = throttleTimer.C 90 | } 91 | var m *Msg 92 | var ok bool 93 | 94 | select { 95 | case m, ok = <-ch: 96 | // nothing 97 | case <-throttleC: 98 | m = &Msg{MsgType: MSGTYPE_FLUSH} 99 | ok = true 100 | } 101 | if !ok { 102 | break 103 | } 104 | switch m.MsgType { 105 | case MSGTYPE_MESSAGE: 106 | mbuf = append(mbuf, m.TimeString()) 107 | if m.Severity < severityTrack { 108 | // keep track of worst severity within a batch of messages 109 | severityTrack = m.Severity 110 | } 111 | case MSGTYPE_ATTACHMENT: 112 | abuf = append(abuf, ">"+ 113 | strings.Replace(strings.TrimRight(m.Text, "\n"), "\n", "\n>", -1)+"\n") 114 | case MSGTYPE_FLUSH: 115 | if len(mbuf) == 0 && len(abuf) == 0 { 116 | continue 117 | } 118 | text := makeEmailText(mbuf, abuf) 119 | if text == "" { 120 | continue 121 | } 122 | if throttle != 0 && time.Since(lastFlush) < throttle { 123 | if throttleTimer == nil { 124 | throttleTimer = time.NewTimer(throttle - time.Since(lastFlush)) 125 | } 126 | continue 127 | } 128 | mbuf, abuf = nil, nil 129 | sevsubject := subject + " [" + severityTrack.String() + "]" 130 | severityTrack = DEBUG 131 | lastFlush = time.Now() 132 | if throttleTimer != nil { 133 | throttleTimer.Stop() 134 | throttleTimer = nil 135 | } 136 | n.wg.Add(1) 137 | go n.sendEmailSMTP(server, username, password, from, to, sevsubject, text) 138 | } 139 | } 140 | // exiting 141 | if throttleTimer != nil { 142 | throttleTimer.Stop() 143 | } 144 | // send the last entries: 145 | if text := makeEmailText(mbuf, abuf); text != "" { 146 | sevsubject := subject + " [" + severityTrack.String() + "]" 147 | n.wg.Add(1) 148 | go n.sendEmailSMTP(server, username, password, from, to, sevsubject, text) 149 | } 150 | } 151 | 152 | // AddLoggerEmailSMTP adds an e-mail logging output. The e-mails are sent 153 | // with ESMTP/SMTP whenever Flush() is called. 154 | func (n *Notifier) AddLoggerEmailSMTP(s Severity, server, user, pass, from, to, subject string, throttle time.Duration) error { 155 | switch { 156 | case s < severity_MIN || s > severity_MAX: 157 | return errors.New(`invalid "severity"`) 158 | case server == "": 159 | return errors.New(`"server" not defined`) 160 | case from == "": 161 | return errors.New(`"from" not defined`) 162 | case to == "": 163 | return errors.New(`"to" not defined`) 164 | case subject == "": 165 | return errors.New(`"subject" not defined`) 166 | } 167 | ch := make(chan *Msg, chan_SIZE) 168 | n.wg.Add(1) 169 | go n.loggerEmailSMTP(ch, server, user, pass, from, to, subject, throttle) 170 | n.out = append(n.out, notifyOutput{severity: s, ch: ch, attachment: true, flush: true}) 171 | return nil 172 | } 173 | 174 | // eof 175 | -------------------------------------------------------------------------------- /notifier/logger_stdout.go: -------------------------------------------------------------------------------- 1 | // 2 | // logger_stdout.go 3 | // 4 | // Copyright © 2012-2013 Damicon Kraa Oy 5 | // 6 | // This file is part of zfswatcher. 7 | // 8 | // Zfswatcher is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // Zfswatcher is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with zfswatcher. If not, see . 20 | // 21 | 22 | package notifier 23 | 24 | import ( 25 | "errors" 26 | "fmt" 27 | "strings" 28 | ) 29 | 30 | func (n *Notifier) loggerStdout(ch chan *Msg) { 31 | defer n.wg.Done() 32 | for m := range ch { 33 | switch m.MsgType { 34 | case MSGTYPE_MESSAGE: 35 | fmt.Println(m.String()) 36 | case MSGTYPE_ATTACHMENT: 37 | fmt.Println(">" + strings.Replace(strings.TrimRight(m.Text, "\n"), "\n", "\n>", -1)) 38 | } 39 | } 40 | } 41 | 42 | // AddLoggerStdout adds a logger which outputs to the standard output. 43 | // This can be useful for running a program in debugging mode. 44 | func (n *Notifier) AddLoggerStdout(s Severity) error { 45 | switch { 46 | case s < severity_MIN || s > severity_MAX: 47 | return errors.New(`invalid "severity"`) 48 | } 49 | ch := make(chan *Msg, chan_SIZE) 50 | n.wg.Add(1) 51 | go n.loggerStdout(ch) 52 | n.out = append(n.out, notifyOutput{severity: s, ch: ch, attachment: true}) 53 | return nil 54 | } 55 | 56 | // eof 57 | -------------------------------------------------------------------------------- /notifier/logger_syslog.go: -------------------------------------------------------------------------------- 1 | // 2 | // logger_syslog.go 3 | // 4 | // Copyright © 2012-2013 Damicon Kraa Oy 5 | // 6 | // This file is part of zfswatcher. 7 | // 8 | // Zfswatcher is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // Zfswatcher is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with zfswatcher. If not, see . 20 | // 21 | 22 | package notifier 23 | 24 | import ( 25 | "errors" 26 | "fmt" 27 | "net" 28 | "os" 29 | "path" 30 | "strings" 31 | "time" 32 | ) 33 | 34 | // This implements the BSD style syslog protocol. 35 | 36 | func connectSyslog(address string) (net.Conn, bool, error) { 37 | conntype := "udp" 38 | sep := false // this is needed later if SOCK_STREAM connections are implemented 39 | 40 | if strings.Index(address, "/") != -1 { 41 | conntype = "unixgram" // XXX should try different types? 42 | } 43 | var c net.Conn 44 | var err error 45 | 46 | for retries := 0; true; retries++ { 47 | c, err = net.Dial(conntype, address) 48 | if err == nil { 49 | break 50 | } 51 | if retries < 3 { 52 | checkInternalError("error connecting syslog socket (retrying)", err) 53 | time.Sleep(retry_SLEEP * time.Millisecond) 54 | } else { 55 | checkInternalError("error connecting syslog socket (giving up)", err) 56 | break 57 | } 58 | } 59 | return c, sep, err 60 | } 61 | 62 | func (n *Notifier) loggerSyslog(ch chan *Msg, address string, facility SyslogFacility) { 63 | defer n.wg.Done() 64 | tag := fmt.Sprintf("%s[%d]", path.Base(os.Args[0]), os.Getpid()) 65 | 66 | var c net.Conn 67 | var sep bool 68 | var err error 69 | var connected bool = false 70 | 71 | c, sep, err = connectSyslog(address) 72 | if err == nil { 73 | connected = true 74 | } 75 | 76 | for m := range ch { 77 | switch m.MsgType { 78 | case MSGTYPE_MESSAGE: 79 | buf := []byte(m.SyslogString(facility, tag)) 80 | 81 | if sep { 82 | if len(buf) > 1023 { 83 | buf = buf[:1022] 84 | } 85 | buf = append(buf, '\n') 86 | } else { 87 | if len(buf) > 1024 { 88 | buf = buf[:1023] 89 | } 90 | } 91 | for retries := 0; retries < 2; retries++ { 92 | if connected { 93 | _, err = c.Write(buf) 94 | if err == nil { 95 | break 96 | } 97 | checkInternalError("error writing to syslog socket", err) 98 | // try to reopen the socket if there was error: 99 | c.Close() 100 | connected = false 101 | } 102 | c, sep, err = connectSyslog(address) 103 | if err == nil { 104 | connected = true 105 | } 106 | } 107 | case MSGTYPE_REOPEN: 108 | if connected { 109 | c.Close() 110 | connected = false 111 | } 112 | c, sep, err = connectSyslog(address) 113 | if err == nil { 114 | connected = true 115 | } 116 | } 117 | } 118 | if connected { 119 | c.Close() 120 | connected = false 121 | } 122 | } 123 | 124 | // AddLoggerSyslog adds a Unix syslog logging output. 125 | func (n *Notifier) AddLoggerSyslog(s Severity, address string, facility SyslogFacility) error { 126 | switch { 127 | case s < severity_MIN || s > severity_MAX: 128 | return errors.New(`invalid "severity"`) 129 | case address == "": 130 | return errors.New(`"address" not defined`) 131 | case facility < syslog_FACILITY_MIN || facility > syslog_FACILITY_MAX: 132 | return errors.New(`invalid "facility"`) 133 | } 134 | ch := make(chan *Msg, chan_SIZE) 135 | n.wg.Add(1) 136 | go n.loggerSyslog(ch, address, facility) 137 | n.out = append(n.out, notifyOutput{severity: s, ch: ch}) 138 | return nil 139 | } 140 | 141 | // eof 142 | -------------------------------------------------------------------------------- /notifier/msg.go: -------------------------------------------------------------------------------- 1 | // 2 | // msg.go 3 | // 4 | // Copyright © 2012-2013 Damicon Kraa Oy 5 | // 6 | // This file is part of zfswatcher. 7 | // 8 | // Zfswatcher is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // Zfswatcher is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with zfswatcher. If not, see . 20 | // 21 | 22 | package notifier 23 | 24 | import ( 25 | "fmt" 26 | "time" 27 | ) 28 | 29 | // A type of a message. 30 | type MsgType int 31 | 32 | const ( 33 | MSGTYPE_MESSAGE MsgType = iota // normal log message 34 | MSGTYPE_ATTACHMENT // additional verbose information 35 | MSGTYPE_FLUSH // send messages to delayed destinations (e-mail) 36 | MSGTYPE_REOPEN // re-open output file after log rotation etc 37 | ) 38 | 39 | // A single message. 40 | type Msg struct { 41 | Time time.Time 42 | MsgType MsgType 43 | Severity Severity 44 | Text string 45 | } 46 | 47 | // String implements the fmt.Stringer interface. It returns the message as 48 | // a single string in human readable format. 49 | func (m *Msg) String() string { 50 | return m.Time.Format(date_time_FORMAT) + 51 | " [" + m.Severity.String() + "] " + 52 | m.Text 53 | } 54 | 55 | // Strings returns the message in three separate strings. 56 | func (m *Msg) Strings() (date_time string, severity string, text string) { 57 | return m.Time.Format(date_time_FORMAT), m.Severity.String(), m.Text 58 | } 59 | 60 | // TimeString is like String() but omits the date from the output. 61 | func (m *Msg) TimeString() string { 62 | return m.Time.Format(time_FORMAT) + 63 | " [" + m.Severity.String() + "] " + 64 | m.Text 65 | } 66 | 67 | // SyslogString returns the message in a format suitable for 68 | // sending to BSD style syslogd. 69 | func (m *Msg) SyslogString(facility SyslogFacility, tag string) string { 70 | return fmt.Sprintf("<%d>%s %s: %s", 71 | uint32(m.Severity)|(uint32(facility)<<3), 72 | m.Time.Format(time.Stamp), tag, m.Text) 73 | } 74 | 75 | // eof 76 | -------------------------------------------------------------------------------- /notifier/notifier.go: -------------------------------------------------------------------------------- 1 | // 2 | // notifier.go 3 | // 4 | // Copyright © 2012-2013 Damicon Kraa Oy 5 | // 6 | // This file is part of zfswatcher. 7 | // 8 | // Zfswatcher is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // Zfswatcher is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with zfswatcher. If not, see . 20 | // 21 | 22 | // Notifier - Go logging package (syslog, file and e-mail) 23 | package notifier 24 | 25 | import ( 26 | "errors" 27 | "fmt" 28 | "os" 29 | "strings" 30 | "sync" 31 | "time" 32 | ) 33 | 34 | const retry_SLEEP = 500 // milliseconds 35 | const chan_SIZE = 32 36 | 37 | // Define the date/time format for outputs. 38 | const ( 39 | time_FORMAT = "15:04:05" 40 | date_FORMAT = "2006-01-02" 41 | date_time_FORMAT = "2006-01-02 15:04:05" 42 | ) 43 | 44 | // private 45 | 46 | func internalError(str string) { 47 | // This is an internal error in the notifier library. 48 | // What should be done with these errors? 49 | // Right now we just write the errors to STDERR. 50 | // This is not a good solution. 51 | fmt.Fprintf(os.Stderr, "%s [NOTIFIER] %s\n", 52 | time.Now().Format(date_time_FORMAT), str) 53 | } 54 | 55 | func checkInternalError(str string, err error) { 56 | if err != nil { 57 | internalError(fmt.Sprintf("%s: %s", str, err)) 58 | } 59 | } 60 | 61 | func (n *Notifier) dispatcher() { 62 | defer n.wg.Done() 63 | // read messages from the input channel: 64 | for m := range n.ch { 65 | // forward the message to relevant loggers 66 | for _, out := range n.out { 67 | switch { 68 | case m.MsgType == MSGTYPE_ATTACHMENT && out.attachment == false: 69 | continue 70 | case m.MsgType == MSGTYPE_FLUSH && out.flush == false: 71 | continue 72 | case m.Severity <= out.severity: 73 | // the zero value of Severity is EMERG, thus 74 | // this always forwards messages with 75 | // undefined severity 76 | select { 77 | case out.ch <- m: 78 | default: 79 | checkInternalError("dispatcher error", errors.New("channel full")) 80 | } 81 | } 82 | } 83 | } 84 | // the input channel has been closed, so close the output channels then: 85 | n.ch = nil 86 | for _, out := range n.out { 87 | close(out.ch) 88 | out.ch = nil 89 | } 90 | } 91 | 92 | // public API 93 | 94 | type notifyOutput struct { 95 | severity Severity 96 | ch chan *Msg 97 | attachment bool 98 | flush bool 99 | } 100 | 101 | // Notifier is a logging subsystem instance which is running as a goroutine 102 | // and may have several different logging destinations. 103 | type Notifier struct { 104 | ch chan *Msg 105 | out []notifyOutput 106 | wg *sync.WaitGroup 107 | } 108 | 109 | // New starts a new logging subsystem as a goroutine. 110 | func New() *Notifier { 111 | ch := make(chan *Msg, chan_SIZE) 112 | n := &Notifier{ch: ch, wg: &sync.WaitGroup{}} 113 | n.wg.Add(1) 114 | go n.dispatcher() 115 | return n 116 | } 117 | 118 | func sanitizeMessageText(str string) string { 119 | // XXX: how about other control characters? 120 | return strings.Replace(str, "\n", " ", -1) 121 | } 122 | 123 | func (n *Notifier) internal_send(msgtype MsgType, s Severity, t string) error { 124 | if s == SEVERITY_NONE { 125 | return nil // discard 126 | } 127 | if s < severity_MIN || s > severity_MAX { 128 | return errors.New(`invalid "severity"`) 129 | } 130 | n.ch <- &Msg{ 131 | Time: time.Now(), 132 | MsgType: msgtype, 133 | Severity: s, 134 | Text: sanitizeMessageText(t), 135 | } 136 | return nil 137 | } 138 | 139 | // Send sends a message for logging. 140 | func (n *Notifier) Send(s Severity, t string) error { 141 | return n.internal_send(MSGTYPE_MESSAGE, s, t) 142 | } 143 | 144 | // Attach sends an attachment for logging. Attachments are usually some 145 | // additional multi-line output which provide further insight into a 146 | // problem. Examples: stack trace in case of panic, parser input in case of 147 | // a parse error. Some logging destinations such as syslog do not support 148 | // logging attachments. For others attachments can be enabled or disabled 149 | // when setting up the logging destination. 150 | func (n *Notifier) Attach(s Severity, t string) error { 151 | return n.internal_send(MSGTYPE_ATTACHMENT, s, t) 152 | } 153 | 154 | // Flush all buffered logging output. This should be called when the program 155 | // finishes "one round". Causes for example e-mails to be sent instead of 156 | // waiting for more log lines. 157 | func (n *Notifier) Flush() { 158 | n.ch <- &Msg{Time: time.Now(), MsgType: MSGTYPE_FLUSH} 159 | } 160 | 161 | // Reopen log outputs. Should be called whenever log files have been rotated. 162 | func (n *Notifier) Reopen() { 163 | n.ch <- &Msg{Time: time.Now(), MsgType: MSGTYPE_REOPEN} 164 | } 165 | 166 | // Close log outputs. Causes the outputs to be flushed and stops the 167 | // goroutines gracefully. Returns a channel which is closed when the 168 | // logging subsystem has shut down. The caller may choose to wait until 169 | // it is closed in case something takes a long time (such as sendind 170 | // an e-mail message). 171 | func (n *Notifier) Close() chan bool { 172 | // close the message channel to tell the goroutines they should quit: 173 | close(n.ch) 174 | // create a channel which can be used to wait for goroutines to quit: 175 | closeC := make(chan bool) 176 | // start a goroutine which closes the channel when all goroutines have quit: 177 | go func() { 178 | n.wg.Wait() 179 | close(closeC) 180 | }() 181 | // return that channel to the caller so they can wait on it if they want: 182 | return closeC 183 | } 184 | 185 | // Printf is normal fmt.Printf which sends a log message. 186 | func (n *Notifier) Printf(s Severity, format string, v ...interface{}) { 187 | n.Send(s, fmt.Sprintf(format, v...)) 188 | } 189 | 190 | // Print is normal fmt.Print which sends a log message. 191 | func (n *Notifier) Print(s Severity, v ...interface{}) { n.Send(s, fmt.Sprint(v...)) } 192 | 193 | // func (n *Notifier) Println(s Severity, v ...interface{}) { n.Send(s, fmt.Sprintln(v...)) } 194 | 195 | // eof 196 | -------------------------------------------------------------------------------- /notifier/severity.go: -------------------------------------------------------------------------------- 1 | // 2 | // severity.go 3 | // 4 | // Copyright © 2012-2013 Damicon Kraa Oy 5 | // 6 | // This file is part of zfswatcher. 7 | // 8 | // Zfswatcher is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // Zfswatcher is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with zfswatcher. If not, see . 20 | // 21 | 22 | package notifier 23 | 24 | import ( 25 | "errors" 26 | "fmt" 27 | ) 28 | 29 | // Message severity levels, matches with syslog severity levels. 30 | type Severity uint32 31 | 32 | const ( 33 | severity_MIN Severity = 0 34 | EMERG Severity = 0 // this is unfortunate 35 | ALERT Severity = 1 36 | CRIT Severity = 2 37 | ERR Severity = 3 38 | WARNING Severity = 4 39 | NOTICE Severity = 5 40 | INFO Severity = 6 41 | DEBUG Severity = 7 42 | severity_MAX Severity = 7 43 | SEVERITY_NONE Severity = 8 // discard these messages 44 | ) 45 | 46 | var severityStrings = []string{ 47 | EMERG: "emerg", 48 | ALERT: "alert", 49 | CRIT: "crit", 50 | ERR: "err", 51 | WARNING: "warning", 52 | NOTICE: "notice", 53 | INFO: "info", 54 | DEBUG: "debug", 55 | SEVERITY_NONE: "none", 56 | } 57 | 58 | var severityCodes = map[string]Severity{ 59 | "emerg": EMERG, 60 | "alert": ALERT, 61 | "crit": CRIT, 62 | "err": ERR, 63 | "error": ERR, 64 | "warn": WARNING, 65 | "warning": WARNING, 66 | "notice": NOTICE, 67 | "info": INFO, 68 | "debug": DEBUG, 69 | "none": SEVERITY_NONE, 70 | } 71 | 72 | // public API 73 | 74 | // Scan implements fmt.Scanner interface. This makes it possible to use 75 | // fmt.Sscan*() functions to parse syslog facility codes. Also 76 | // "gcfg" package can parse them in configuration files. 77 | func (s *Severity) Scan(state fmt.ScanState, verb rune) error { 78 | sevstr, err := state.Token(false, func(r rune) bool { return true }) 79 | if err != nil { 80 | return err 81 | } 82 | sev, ok := severityCodes[string(sevstr)] 83 | if !ok { 84 | return errors.New(`invalid severity "` + string(sevstr) + `"`) 85 | } 86 | *s = sev 87 | return nil 88 | } 89 | 90 | // String implements fmt.Stringer interface. 91 | func (s Severity) String() string { 92 | return severityStrings[s] 93 | } 94 | 95 | // eof 96 | -------------------------------------------------------------------------------- /osutil_freebsd.go: -------------------------------------------------------------------------------- 1 | // 2 | // osutil_freebsd.go 3 | // 4 | // Copyright © 2012-2013 Damicon Kraa Oy 5 | // 6 | // This file is part of zfswatcher. 7 | // 8 | // Zfswatcher is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // Zfswatcher is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with zfswatcher. If not, see . 20 | // 21 | 22 | package main 23 | 24 | // #include 25 | import "C" 26 | 27 | import ( 28 | "errors" 29 | "syscall" 30 | "time" 31 | "unsafe" 32 | ) 33 | 34 | // Returns system uptime as time.Duration. 35 | func getSystemUptime() (uptime time.Duration, err error) { 36 | val, err := syscall.Sysctl("kern.boottime") 37 | if err != nil { 38 | return 0, err 39 | } 40 | buf := []byte(val) 41 | tv := *syscall.Timeval(unsafe.Pointer(&buf[0])) 42 | 43 | return time.Since(time.Unix(tv.Unix())), nil 44 | } 45 | 46 | // Returns system load averages. 47 | func getSystemLoadaverage() ([3]float32, error) { 48 | avg := []C.double{0, 0, 0} 49 | 50 | n := C.getloadavg(&avg[0], C.int(len(avg))) 51 | 52 | if n == -1 { 53 | return [3]float32{0, 0, 0}, errors.New("load average unavailable") 54 | } 55 | return [3]float32{float32(avg[0]), float32(avg[1]), float32(avg[2])}, nil 56 | } 57 | 58 | // Device lookup paths. (This list comes from lib/libzfs/libzfs_import.c) 59 | var deviceLookupPaths = [...]string{ 60 | "/dev", 61 | } 62 | 63 | // eof 64 | -------------------------------------------------------------------------------- /osutil_linux.go: -------------------------------------------------------------------------------- 1 | // 2 | // osutil_linux.go 3 | // 4 | // Copyright © 2012-2013 Damicon Kraa Oy 5 | // 6 | // This file is part of zfswatcher. 7 | // 8 | // Zfswatcher is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // Zfswatcher is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with zfswatcher. If not, see . 20 | // 21 | 22 | package main 23 | 24 | import ( 25 | "errors" 26 | "fmt" 27 | "io/ioutil" 28 | "time" 29 | ) 30 | 31 | // Returns system uptime as time.Duration. 32 | func getSystemUptime() (uptime time.Duration, err error) { 33 | buf, err := ioutil.ReadFile("/proc/uptime") 34 | if err != nil { 35 | return uptime, err 36 | } 37 | var up, idle float64 38 | n, err := fmt.Sscanln(string(buf), &up, &idle) 39 | if err != nil { 40 | return uptime, err 41 | } 42 | if n != 2 { 43 | return uptime, errors.New("failed parsing /proc/uptime") 44 | } 45 | uptime = time.Duration(up) * time.Second 46 | 47 | return uptime, nil 48 | } 49 | 50 | // Returns system load averages. 51 | func getSystemLoadaverage() (la [3]float32, err error) { 52 | buf, err := ioutil.ReadFile("/proc/loadavg") 53 | if err != nil { 54 | return la, err 55 | } 56 | n, err := fmt.Sscan(string(buf), &la[0], &la[1], &la[2]) 57 | if err != nil { 58 | return la, err 59 | } 60 | if n != 3 { 61 | return la, errors.New("failed parsing /proc/loadavg") 62 | } 63 | 64 | return la, nil 65 | } 66 | 67 | // Device lookup paths. (This list comes from lib/libzfs/libzfs_import.c) 68 | var deviceLookupPaths = [...]string{ 69 | "/dev/disk/by-vdev", 70 | "/dev/disk/zpool", 71 | "/dev/mapper", 72 | "/dev/disk/by-uuid", 73 | "/dev/disk/by-id", 74 | "/dev/disk/by-path", 75 | "/dev/disk/by-label", 76 | "/dev", 77 | } 78 | 79 | // eof 80 | -------------------------------------------------------------------------------- /osutil_solaris.go: -------------------------------------------------------------------------------- 1 | // 2 | // osutil_solaris.go 3 | // 4 | // Copyright © 2012-2013 Damicon Kraa Oy 5 | // 6 | // This file is part of zfswatcher. 7 | // 8 | // Zfswatcher is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // Zfswatcher is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with zfswatcher. If not, see . 20 | // 21 | 22 | // +build solaris 23 | 24 | package main 25 | 26 | import ( 27 | "time" 28 | ) 29 | 30 | // Returns system uptime as time.Duration. 31 | func getSystemUptime() (uptime time.Duration, err error) { 32 | // XXX 33 | return 0, nil 34 | } 35 | 36 | // Returns system load averages. 37 | func getSystemLoadaverage() ([3]float32, error) { 38 | // XXX 39 | return [3]float32{0, 0, 0}, nil 40 | } 41 | 42 | // Device lookup paths. (This list comes from lib/libzfs/libzfs_import.c) 43 | var deviceLookupPaths = [...]string{ 44 | "/dev/dsk", 45 | } 46 | 47 | // eof 48 | -------------------------------------------------------------------------------- /test/zfs-list-details.txt: -------------------------------------------------------------------------------- 1 | tank 155G 380G 100M 154G 0 227G 154G /tank 2 | tank@snaptest - 100M - - - - 20.1G - 3 | tank/test-8k 157G 103G 0 101G 1.72G 0 101G - 4 | tank/xfs 258G 123G 165K 20.1G 103G 0 20.1G - 5 | tank/xfs@backup - 165K - - - - 20.1G - 6 | -------------------------------------------------------------------------------- /test/zfs-list.txt: -------------------------------------------------------------------------------- 1 | tank 155G 380G 100M 154G 0 227G 154G /tank 2 | vmstore 49.4G 17.5G 0 31K 0 17.5G 31K /vmstore 3 | -------------------------------------------------------------------------------- /test/zpool-status-2pools.txt: -------------------------------------------------------------------------------- 1 | pool: tank 2 | state: ONLINE 3 | scan: none requested 4 | config: 5 | 6 | NAME STATE READ WRITE CKSUM 7 | tank ONLINE 0 0 0 8 | mirror-0 ONLINE 0 0 0 9 | scsi-3500000e01a0fc9f0 ONLINE 0 0 0 10 | scsi-3500000e014bb4140 ONLINE 0 0 0 11 | mirror-1 ONLINE 0 0 0 12 | scsi-3500000e015060e90 ONLINE 0 0 0 13 | scsi-3500000e014d72a30 ONLINE 0 0 0 14 | mirror-2 ONLINE 0 0 0 15 | scsi-3500000e014fefc20 ONLINE 0 0 0 16 | scsi-3500000e01594c2a0 ONLINE 0 0 0 17 | mirror-3 ONLINE 0 0 0 18 | scsi-3500000e01519b340 ONLINE 0 0 0 19 | scsi-3500000e01594bd90 ONLINE 0 0 0 20 | mirror-4 ONLINE 0 0 0 21 | scsi-35000c500031e1ef3 ONLINE 0 0 0 22 | scsi-3500000e015193820 ONLINE 0 0 0 23 | mirror-5 ONLINE 0 0 0 24 | scsi-3500000e0142e78c0 ONLINE 0 0 0 25 | scsi-3500000e01675d620 ONLINE 0 0 0 26 | mirror-6 ONLINE 0 0 0 27 | scsi-3500000e01aff8fd0 ONLINE 0 0 0 28 | scsi-35000c500031e54f3 ONLINE 0 0 0 29 | mirror-7 ONLINE 0 0 0 30 | scsi-3500000e015904b30 ONLINE 0 0 0 31 | scsi-3500000e015949b60 ONLINE 0 0 0 32 | logs 33 | sdc ONLINE 0 0 0 34 | 35 | errors: No known data errors 36 | 37 | pool: vmstore 38 | state: ONLINE 39 | scan: none requested 40 | config: 41 | 42 | NAME STATE READ WRITE CKSUM 43 | vmstore ONLINE 0 0 0 44 | sdt ONLINE 0 0 0 45 | 46 | errors: No known data errors 47 | -------------------------------------------------------------------------------- /test/zpool-status-degraded.txt: -------------------------------------------------------------------------------- 1 | pool: test 2 | state: DEGRADED 3 | status: One or more devices could not be used because the label is missing or 4 | invalid. Sufficient replicas exist for the pool to continue 5 | functioning in a degraded state. 6 | action: Replace the device using 'zpool replace'. 7 | see: http://zfsonlinux.org/msg/ZFS-8000-4J 8 | scan: resilvered 674M in 0h3m with 0 errors on Tue Dec 18 00:19:59 2012 9 | config: 10 | 11 | NAME STATE READ WRITE CKSUM 12 | test DEGRADED 0 0 0 13 | raidz3-0 DEGRADED 0 0 0 14 | B0 UNAVAIL 0 0 0 15 | A1 ONLINE 0 0 0 16 | A2 ONLINE 0 0 0 17 | A3 ONLINE 0 0 0 18 | A4 ONLINE 0 0 0 19 | A5 ONLINE 0 0 0 20 | A6 ONLINE 0 0 0 21 | spares 22 | B1 UNAVAIL 23 | B2 UNAVAIL 24 | 25 | errors: No known data errors 26 | -------------------------------------------------------------------------------- /version.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | const VERSION = "0.03" 4 | -------------------------------------------------------------------------------- /webserver.go: -------------------------------------------------------------------------------- 1 | // 2 | // webserver.go 3 | // 4 | // Copyright © 2012-2013 Damicon Kraa Oy 5 | // 6 | // This file is part of zfswatcher. 7 | // 8 | // Zfswatcher is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // Zfswatcher is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with zfswatcher. If not, see . 20 | // 21 | 22 | package main 23 | 24 | import ( 25 | "fmt" 26 | auth "github.com/abbot/go-http-auth" 27 | "github.com/damicon/zfswatcher/notifier" 28 | "html/template" 29 | "math/rand" 30 | "net/http" 31 | "strings" 32 | "time" 33 | ) 34 | 35 | func getUserSecret(username, realm string) string { 36 | if username == "" { 37 | return "" 38 | } 39 | user, ok := cfg.Wwwuser[username] 40 | if !ok { 41 | return "" 42 | } 43 | if user.Enable { 44 | return user.Password 45 | } 46 | return "" 47 | } 48 | 49 | func noDirListing(h http.Handler) http.HandlerFunc { 50 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 51 | if strings.HasSuffix(r.URL.Path, "/") { 52 | http.NotFound(w, r) 53 | return 54 | } 55 | h.ServeHTTP(w, r) 56 | }) 57 | } 58 | 59 | func webServer() { 60 | var err error 61 | 62 | templates = template.New("zfswatcher").Funcs(template.FuncMap{ 63 | "nicenumber": niceNumber, 64 | }) 65 | templates, err = templates.ParseGlob(cfg.Www.Templatedir + "/*.html") 66 | if err != nil { 67 | notify.Printf(notifier.ERR, "error parsing templates: %s", err) 68 | } 69 | 70 | authenticator := auth.NewBasicAuthenticator("zfswatcher", getUserSecret) 71 | 72 | http.Handle("/resources/", 73 | http.StripPrefix("/resources", 74 | noDirListing( 75 | authenticator.WrapHandler( 76 | http.FileServer( 77 | http.Dir(cfg.Www.Resourcedir)))))) 78 | 79 | http.HandleFunc("/", authenticator.Wrap(dashboardHandler)) 80 | http.HandleFunc("/status/", authenticator.Wrap(statusHandler)) 81 | http.HandleFunc("/usage/", authenticator.Wrap(usageHandler)) 82 | http.HandleFunc("/statistics/", authenticator.Wrap(statisticsHandler)) 83 | http.HandleFunc("/logs/", authenticator.Wrap(logsHandler)) 84 | http.HandleFunc("/about/", authenticator.Wrap(aboutHandler)) 85 | http.HandleFunc("/locate/", authenticator.Wrap(locateHandler)) 86 | 87 | if cfg.Www.Certfile != "" && cfg.Www.Keyfile != "" { 88 | err = http.ListenAndServeTLS(cfg.Www.Bind, cfg.Www.Certfile, cfg.Www.Keyfile, nil) 89 | } else { 90 | err = http.ListenAndServe(cfg.Www.Bind, nil) 91 | } 92 | if err != nil { 93 | notify.Printf(notifier.ERR, "error starting web server: %s", err) 94 | } 95 | } 96 | 97 | func wwwHashPassword() { 98 | fmt.Printf("Password (will echo): ") 99 | var password string 100 | _, err := fmt.Scanln(&password) 101 | if err != nil { 102 | fmt.Println("Error:", err) 103 | return 104 | } 105 | rand.Seed(time.Now().UnixNano()) 106 | 107 | base64alpha := "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" 108 | var salt []byte 109 | for i := 0; i < 8; i++ { 110 | salt = append(salt, base64alpha[rand.Intn(len(base64alpha))]) 111 | } 112 | 113 | hash := auth.MD5Crypt([]byte(password), salt, []byte("$1$")) 114 | fmt.Println("Hash:", string(hash)) 115 | } 116 | 117 | // eof 118 | -------------------------------------------------------------------------------- /www/resources/bootstrap/js/bootstrap-affix.js: -------------------------------------------------------------------------------- 1 | /* ========================================================== 2 | * bootstrap-affix.js v2.2.2 3 | * http://twitter.github.com/bootstrap/javascript.html#affix 4 | * ========================================================== 5 | * Copyright 2012 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ========================================================== */ 19 | 20 | 21 | !function ($) { 22 | 23 | "use strict"; // jshint ;_; 24 | 25 | 26 | /* AFFIX CLASS DEFINITION 27 | * ====================== */ 28 | 29 | var Affix = function (element, options) { 30 | this.options = $.extend({}, $.fn.affix.defaults, options) 31 | this.$window = $(window) 32 | .on('scroll.affix.data-api', $.proxy(this.checkPosition, this)) 33 | .on('click.affix.data-api', $.proxy(function () { setTimeout($.proxy(this.checkPosition, this), 1) }, this)) 34 | this.$element = $(element) 35 | this.checkPosition() 36 | } 37 | 38 | Affix.prototype.checkPosition = function () { 39 | if (!this.$element.is(':visible')) return 40 | 41 | var scrollHeight = $(document).height() 42 | , scrollTop = this.$window.scrollTop() 43 | , position = this.$element.offset() 44 | , offset = this.options.offset 45 | , offsetBottom = offset.bottom 46 | , offsetTop = offset.top 47 | , reset = 'affix affix-top affix-bottom' 48 | , affix 49 | 50 | if (typeof offset != 'object') offsetBottom = offsetTop = offset 51 | if (typeof offsetTop == 'function') offsetTop = offset.top() 52 | if (typeof offsetBottom == 'function') offsetBottom = offset.bottom() 53 | 54 | affix = this.unpin != null && (scrollTop + this.unpin <= position.top) ? 55 | false : offsetBottom != null && (position.top + this.$element.height() >= scrollHeight - offsetBottom) ? 56 | 'bottom' : offsetTop != null && scrollTop <= offsetTop ? 57 | 'top' : false 58 | 59 | if (this.affixed === affix) return 60 | 61 | this.affixed = affix 62 | this.unpin = affix == 'bottom' ? position.top - scrollTop : null 63 | 64 | this.$element.removeClass(reset).addClass('affix' + (affix ? '-' + affix : '')) 65 | } 66 | 67 | 68 | /* AFFIX PLUGIN DEFINITION 69 | * ======================= */ 70 | 71 | var old = $.fn.affix 72 | 73 | $.fn.affix = function (option) { 74 | return this.each(function () { 75 | var $this = $(this) 76 | , data = $this.data('affix') 77 | , options = typeof option == 'object' && option 78 | if (!data) $this.data('affix', (data = new Affix(this, options))) 79 | if (typeof option == 'string') data[option]() 80 | }) 81 | } 82 | 83 | $.fn.affix.Constructor = Affix 84 | 85 | $.fn.affix.defaults = { 86 | offset: 0 87 | } 88 | 89 | 90 | /* AFFIX NO CONFLICT 91 | * ================= */ 92 | 93 | $.fn.affix.noConflict = function () { 94 | $.fn.affix = old 95 | return this 96 | } 97 | 98 | 99 | /* AFFIX DATA-API 100 | * ============== */ 101 | 102 | $(window).on('load', function () { 103 | $('[data-spy="affix"]').each(function () { 104 | var $spy = $(this) 105 | , data = $spy.data() 106 | 107 | data.offset = data.offset || {} 108 | 109 | data.offsetBottom && (data.offset.bottom = data.offsetBottom) 110 | data.offsetTop && (data.offset.top = data.offsetTop) 111 | 112 | $spy.affix(data) 113 | }) 114 | }) 115 | 116 | 117 | }(window.jQuery); -------------------------------------------------------------------------------- /www/resources/bootstrap/js/bootstrap-alert.js: -------------------------------------------------------------------------------- 1 | /* ========================================================== 2 | * bootstrap-alert.js v2.2.2 3 | * http://twitter.github.com/bootstrap/javascript.html#alerts 4 | * ========================================================== 5 | * Copyright 2012 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ========================================================== */ 19 | 20 | 21 | !function ($) { 22 | 23 | "use strict"; // jshint ;_; 24 | 25 | 26 | /* ALERT CLASS DEFINITION 27 | * ====================== */ 28 | 29 | var dismiss = '[data-dismiss="alert"]' 30 | , Alert = function (el) { 31 | $(el).on('click', dismiss, this.close) 32 | } 33 | 34 | Alert.prototype.close = function (e) { 35 | var $this = $(this) 36 | , selector = $this.attr('data-target') 37 | , $parent 38 | 39 | if (!selector) { 40 | selector = $this.attr('href') 41 | selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 42 | } 43 | 44 | $parent = $(selector) 45 | 46 | e && e.preventDefault() 47 | 48 | $parent.length || ($parent = $this.hasClass('alert') ? $this : $this.parent()) 49 | 50 | $parent.trigger(e = $.Event('close')) 51 | 52 | if (e.isDefaultPrevented()) return 53 | 54 | $parent.removeClass('in') 55 | 56 | function removeElement() { 57 | $parent 58 | .trigger('closed') 59 | .remove() 60 | } 61 | 62 | $.support.transition && $parent.hasClass('fade') ? 63 | $parent.on($.support.transition.end, removeElement) : 64 | removeElement() 65 | } 66 | 67 | 68 | /* ALERT PLUGIN DEFINITION 69 | * ======================= */ 70 | 71 | var old = $.fn.alert 72 | 73 | $.fn.alert = function (option) { 74 | return this.each(function () { 75 | var $this = $(this) 76 | , data = $this.data('alert') 77 | if (!data) $this.data('alert', (data = new Alert(this))) 78 | if (typeof option == 'string') data[option].call($this) 79 | }) 80 | } 81 | 82 | $.fn.alert.Constructor = Alert 83 | 84 | 85 | /* ALERT NO CONFLICT 86 | * ================= */ 87 | 88 | $.fn.alert.noConflict = function () { 89 | $.fn.alert = old 90 | return this 91 | } 92 | 93 | 94 | /* ALERT DATA-API 95 | * ============== */ 96 | 97 | $(document).on('click.alert.data-api', dismiss, Alert.prototype.close) 98 | 99 | }(window.jQuery); -------------------------------------------------------------------------------- /www/resources/bootstrap/js/bootstrap-button.js: -------------------------------------------------------------------------------- 1 | /* ============================================================ 2 | * bootstrap-button.js v2.2.2 3 | * http://twitter.github.com/bootstrap/javascript.html#buttons 4 | * ============================================================ 5 | * Copyright 2012 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ============================================================ */ 19 | 20 | 21 | !function ($) { 22 | 23 | "use strict"; // jshint ;_; 24 | 25 | 26 | /* BUTTON PUBLIC CLASS DEFINITION 27 | * ============================== */ 28 | 29 | var Button = function (element, options) { 30 | this.$element = $(element) 31 | this.options = $.extend({}, $.fn.button.defaults, options) 32 | } 33 | 34 | Button.prototype.setState = function (state) { 35 | var d = 'disabled' 36 | , $el = this.$element 37 | , data = $el.data() 38 | , val = $el.is('input') ? 'val' : 'html' 39 | 40 | state = state + 'Text' 41 | data.resetText || $el.data('resetText', $el[val]()) 42 | 43 | $el[val](data[state] || this.options[state]) 44 | 45 | // push to event loop to allow forms to submit 46 | setTimeout(function () { 47 | state == 'loadingText' ? 48 | $el.addClass(d).attr(d, d) : 49 | $el.removeClass(d).removeAttr(d) 50 | }, 0) 51 | } 52 | 53 | Button.prototype.toggle = function () { 54 | var $parent = this.$element.closest('[data-toggle="buttons-radio"]') 55 | 56 | $parent && $parent 57 | .find('.active') 58 | .removeClass('active') 59 | 60 | this.$element.toggleClass('active') 61 | } 62 | 63 | 64 | /* BUTTON PLUGIN DEFINITION 65 | * ======================== */ 66 | 67 | var old = $.fn.button 68 | 69 | $.fn.button = function (option) { 70 | return this.each(function () { 71 | var $this = $(this) 72 | , data = $this.data('button') 73 | , options = typeof option == 'object' && option 74 | if (!data) $this.data('button', (data = new Button(this, options))) 75 | if (option == 'toggle') data.toggle() 76 | else if (option) data.setState(option) 77 | }) 78 | } 79 | 80 | $.fn.button.defaults = { 81 | loadingText: 'loading...' 82 | } 83 | 84 | $.fn.button.Constructor = Button 85 | 86 | 87 | /* BUTTON NO CONFLICT 88 | * ================== */ 89 | 90 | $.fn.button.noConflict = function () { 91 | $.fn.button = old 92 | return this 93 | } 94 | 95 | 96 | /* BUTTON DATA-API 97 | * =============== */ 98 | 99 | $(document).on('click.button.data-api', '[data-toggle^=button]', function (e) { 100 | var $btn = $(e.target) 101 | if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn') 102 | $btn.button('toggle') 103 | }) 104 | 105 | }(window.jQuery); -------------------------------------------------------------------------------- /www/resources/bootstrap/js/bootstrap-carousel.js: -------------------------------------------------------------------------------- 1 | /* ========================================================== 2 | * bootstrap-carousel.js v2.2.2 3 | * http://twitter.github.com/bootstrap/javascript.html#carousel 4 | * ========================================================== 5 | * Copyright 2012 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ========================================================== */ 19 | 20 | 21 | !function ($) { 22 | 23 | "use strict"; // jshint ;_; 24 | 25 | 26 | /* CAROUSEL CLASS DEFINITION 27 | * ========================= */ 28 | 29 | var Carousel = function (element, options) { 30 | this.$element = $(element) 31 | this.options = options 32 | this.options.pause == 'hover' && this.$element 33 | .on('mouseenter', $.proxy(this.pause, this)) 34 | .on('mouseleave', $.proxy(this.cycle, this)) 35 | } 36 | 37 | Carousel.prototype = { 38 | 39 | cycle: function (e) { 40 | if (!e) this.paused = false 41 | this.options.interval 42 | && !this.paused 43 | && (this.interval = setInterval($.proxy(this.next, this), this.options.interval)) 44 | return this 45 | } 46 | 47 | , to: function (pos) { 48 | var $active = this.$element.find('.item.active') 49 | , children = $active.parent().children() 50 | , activePos = children.index($active) 51 | , that = this 52 | 53 | if (pos > (children.length - 1) || pos < 0) return 54 | 55 | if (this.sliding) { 56 | return this.$element.one('slid', function () { 57 | that.to(pos) 58 | }) 59 | } 60 | 61 | if (activePos == pos) { 62 | return this.pause().cycle() 63 | } 64 | 65 | return this.slide(pos > activePos ? 'next' : 'prev', $(children[pos])) 66 | } 67 | 68 | , pause: function (e) { 69 | if (!e) this.paused = true 70 | if (this.$element.find('.next, .prev').length && $.support.transition.end) { 71 | this.$element.trigger($.support.transition.end) 72 | this.cycle() 73 | } 74 | clearInterval(this.interval) 75 | this.interval = null 76 | return this 77 | } 78 | 79 | , next: function () { 80 | if (this.sliding) return 81 | return this.slide('next') 82 | } 83 | 84 | , prev: function () { 85 | if (this.sliding) return 86 | return this.slide('prev') 87 | } 88 | 89 | , slide: function (type, next) { 90 | var $active = this.$element.find('.item.active') 91 | , $next = next || $active[type]() 92 | , isCycling = this.interval 93 | , direction = type == 'next' ? 'left' : 'right' 94 | , fallback = type == 'next' ? 'first' : 'last' 95 | , that = this 96 | , e 97 | 98 | this.sliding = true 99 | 100 | isCycling && this.pause() 101 | 102 | $next = $next.length ? $next : this.$element.find('.item')[fallback]() 103 | 104 | e = $.Event('slide', { 105 | relatedTarget: $next[0] 106 | }) 107 | 108 | if ($next.hasClass('active')) return 109 | 110 | if ($.support.transition && this.$element.hasClass('slide')) { 111 | this.$element.trigger(e) 112 | if (e.isDefaultPrevented()) return 113 | $next.addClass(type) 114 | $next[0].offsetWidth // force reflow 115 | $active.addClass(direction) 116 | $next.addClass(direction) 117 | this.$element.one($.support.transition.end, function () { 118 | $next.removeClass([type, direction].join(' ')).addClass('active') 119 | $active.removeClass(['active', direction].join(' ')) 120 | that.sliding = false 121 | setTimeout(function () { that.$element.trigger('slid') }, 0) 122 | }) 123 | } else { 124 | this.$element.trigger(e) 125 | if (e.isDefaultPrevented()) return 126 | $active.removeClass('active') 127 | $next.addClass('active') 128 | this.sliding = false 129 | this.$element.trigger('slid') 130 | } 131 | 132 | isCycling && this.cycle() 133 | 134 | return this 135 | } 136 | 137 | } 138 | 139 | 140 | /* CAROUSEL PLUGIN DEFINITION 141 | * ========================== */ 142 | 143 | var old = $.fn.carousel 144 | 145 | $.fn.carousel = function (option) { 146 | return this.each(function () { 147 | var $this = $(this) 148 | , data = $this.data('carousel') 149 | , options = $.extend({}, $.fn.carousel.defaults, typeof option == 'object' && option) 150 | , action = typeof option == 'string' ? option : options.slide 151 | if (!data) $this.data('carousel', (data = new Carousel(this, options))) 152 | if (typeof option == 'number') data.to(option) 153 | else if (action) data[action]() 154 | else if (options.interval) data.cycle() 155 | }) 156 | } 157 | 158 | $.fn.carousel.defaults = { 159 | interval: 5000 160 | , pause: 'hover' 161 | } 162 | 163 | $.fn.carousel.Constructor = Carousel 164 | 165 | 166 | /* CAROUSEL NO CONFLICT 167 | * ==================== */ 168 | 169 | $.fn.carousel.noConflict = function () { 170 | $.fn.carousel = old 171 | return this 172 | } 173 | 174 | /* CAROUSEL DATA-API 175 | * ================= */ 176 | 177 | $(document).on('click.carousel.data-api', '[data-slide]', function (e) { 178 | var $this = $(this), href 179 | , $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7 180 | , options = $.extend({}, $target.data(), $this.data()) 181 | $target.carousel(options) 182 | e.preventDefault() 183 | }) 184 | 185 | }(window.jQuery); -------------------------------------------------------------------------------- /www/resources/bootstrap/js/bootstrap-collapse.js: -------------------------------------------------------------------------------- 1 | /* ============================================================= 2 | * bootstrap-collapse.js v2.2.2 3 | * http://twitter.github.com/bootstrap/javascript.html#collapse 4 | * ============================================================= 5 | * Copyright 2012 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ============================================================ */ 19 | 20 | 21 | !function ($) { 22 | 23 | "use strict"; // jshint ;_; 24 | 25 | 26 | /* COLLAPSE PUBLIC CLASS DEFINITION 27 | * ================================ */ 28 | 29 | var Collapse = function (element, options) { 30 | this.$element = $(element) 31 | this.options = $.extend({}, $.fn.collapse.defaults, options) 32 | 33 | if (this.options.parent) { 34 | this.$parent = $(this.options.parent) 35 | } 36 | 37 | this.options.toggle && this.toggle() 38 | } 39 | 40 | Collapse.prototype = { 41 | 42 | constructor: Collapse 43 | 44 | , dimension: function () { 45 | var hasWidth = this.$element.hasClass('width') 46 | return hasWidth ? 'width' : 'height' 47 | } 48 | 49 | , show: function () { 50 | var dimension 51 | , scroll 52 | , actives 53 | , hasData 54 | 55 | if (this.transitioning) return 56 | 57 | dimension = this.dimension() 58 | scroll = $.camelCase(['scroll', dimension].join('-')) 59 | actives = this.$parent && this.$parent.find('> .accordion-group > .in') 60 | 61 | if (actives && actives.length) { 62 | hasData = actives.data('collapse') 63 | if (hasData && hasData.transitioning) return 64 | actives.collapse('hide') 65 | hasData || actives.data('collapse', null) 66 | } 67 | 68 | this.$element[dimension](0) 69 | this.transition('addClass', $.Event('show'), 'shown') 70 | $.support.transition && this.$element[dimension](this.$element[0][scroll]) 71 | } 72 | 73 | , hide: function () { 74 | var dimension 75 | if (this.transitioning) return 76 | dimension = this.dimension() 77 | this.reset(this.$element[dimension]()) 78 | this.transition('removeClass', $.Event('hide'), 'hidden') 79 | this.$element[dimension](0) 80 | } 81 | 82 | , reset: function (size) { 83 | var dimension = this.dimension() 84 | 85 | this.$element 86 | .removeClass('collapse') 87 | [dimension](size || 'auto') 88 | [0].offsetWidth 89 | 90 | this.$element[size !== null ? 'addClass' : 'removeClass']('collapse') 91 | 92 | return this 93 | } 94 | 95 | , transition: function (method, startEvent, completeEvent) { 96 | var that = this 97 | , complete = function () { 98 | if (startEvent.type == 'show') that.reset() 99 | that.transitioning = 0 100 | that.$element.trigger(completeEvent) 101 | } 102 | 103 | this.$element.trigger(startEvent) 104 | 105 | if (startEvent.isDefaultPrevented()) return 106 | 107 | this.transitioning = 1 108 | 109 | this.$element[method]('in') 110 | 111 | $.support.transition && this.$element.hasClass('collapse') ? 112 | this.$element.one($.support.transition.end, complete) : 113 | complete() 114 | } 115 | 116 | , toggle: function () { 117 | this[this.$element.hasClass('in') ? 'hide' : 'show']() 118 | } 119 | 120 | } 121 | 122 | 123 | /* COLLAPSE PLUGIN DEFINITION 124 | * ========================== */ 125 | 126 | var old = $.fn.collapse 127 | 128 | $.fn.collapse = function (option) { 129 | return this.each(function () { 130 | var $this = $(this) 131 | , data = $this.data('collapse') 132 | , options = typeof option == 'object' && option 133 | if (!data) $this.data('collapse', (data = new Collapse(this, options))) 134 | if (typeof option == 'string') data[option]() 135 | }) 136 | } 137 | 138 | $.fn.collapse.defaults = { 139 | toggle: true 140 | } 141 | 142 | $.fn.collapse.Constructor = Collapse 143 | 144 | 145 | /* COLLAPSE NO CONFLICT 146 | * ==================== */ 147 | 148 | $.fn.collapse.noConflict = function () { 149 | $.fn.collapse = old 150 | return this 151 | } 152 | 153 | 154 | /* COLLAPSE DATA-API 155 | * ================= */ 156 | 157 | $(document).on('click.collapse.data-api', '[data-toggle=collapse]', function (e) { 158 | var $this = $(this), href 159 | , target = $this.attr('data-target') 160 | || e.preventDefault() 161 | || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7 162 | , option = $(target).data('collapse') ? 'toggle' : $this.data() 163 | $this[$(target).hasClass('in') ? 'addClass' : 'removeClass']('collapsed') 164 | $(target).collapse(option) 165 | }) 166 | 167 | }(window.jQuery); -------------------------------------------------------------------------------- /www/resources/bootstrap/js/bootstrap-dropdown.js: -------------------------------------------------------------------------------- 1 | /* ============================================================ 2 | * bootstrap-dropdown.js v2.2.2 3 | * http://twitter.github.com/bootstrap/javascript.html#dropdowns 4 | * ============================================================ 5 | * Copyright 2012 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ============================================================ */ 19 | 20 | 21 | !function ($) { 22 | 23 | "use strict"; // jshint ;_; 24 | 25 | 26 | /* DROPDOWN CLASS DEFINITION 27 | * ========================= */ 28 | 29 | var toggle = '[data-toggle=dropdown]' 30 | , Dropdown = function (element) { 31 | var $el = $(element).on('click.dropdown.data-api', this.toggle) 32 | $('html').on('click.dropdown.data-api', function () { 33 | $el.parent().removeClass('open') 34 | }) 35 | } 36 | 37 | Dropdown.prototype = { 38 | 39 | constructor: Dropdown 40 | 41 | , toggle: function (e) { 42 | var $this = $(this) 43 | , $parent 44 | , isActive 45 | 46 | if ($this.is('.disabled, :disabled')) return 47 | 48 | $parent = getParent($this) 49 | 50 | isActive = $parent.hasClass('open') 51 | 52 | clearMenus() 53 | 54 | if (!isActive) { 55 | $parent.toggleClass('open') 56 | } 57 | 58 | $this.focus() 59 | 60 | return false 61 | } 62 | 63 | , keydown: function (e) { 64 | var $this 65 | , $items 66 | , $active 67 | , $parent 68 | , isActive 69 | , index 70 | 71 | if (!/(38|40|27)/.test(e.keyCode)) return 72 | 73 | $this = $(this) 74 | 75 | e.preventDefault() 76 | e.stopPropagation() 77 | 78 | if ($this.is('.disabled, :disabled')) return 79 | 80 | $parent = getParent($this) 81 | 82 | isActive = $parent.hasClass('open') 83 | 84 | if (!isActive || (isActive && e.keyCode == 27)) return $this.click() 85 | 86 | $items = $('[role=menu] li:not(.divider):visible a', $parent) 87 | 88 | if (!$items.length) return 89 | 90 | index = $items.index($items.filter(':focus')) 91 | 92 | if (e.keyCode == 38 && index > 0) index-- // up 93 | if (e.keyCode == 40 && index < $items.length - 1) index++ // down 94 | if (!~index) index = 0 95 | 96 | $items 97 | .eq(index) 98 | .focus() 99 | } 100 | 101 | } 102 | 103 | function clearMenus() { 104 | $(toggle).each(function () { 105 | getParent($(this)).removeClass('open') 106 | }) 107 | } 108 | 109 | function getParent($this) { 110 | var selector = $this.attr('data-target') 111 | , $parent 112 | 113 | if (!selector) { 114 | selector = $this.attr('href') 115 | selector = selector && /#/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 116 | } 117 | 118 | $parent = $(selector) 119 | $parent.length || ($parent = $this.parent()) 120 | 121 | return $parent 122 | } 123 | 124 | 125 | /* DROPDOWN PLUGIN DEFINITION 126 | * ========================== */ 127 | 128 | var old = $.fn.dropdown 129 | 130 | $.fn.dropdown = function (option) { 131 | return this.each(function () { 132 | var $this = $(this) 133 | , data = $this.data('dropdown') 134 | if (!data) $this.data('dropdown', (data = new Dropdown(this))) 135 | if (typeof option == 'string') data[option].call($this) 136 | }) 137 | } 138 | 139 | $.fn.dropdown.Constructor = Dropdown 140 | 141 | 142 | /* DROPDOWN NO CONFLICT 143 | * ==================== */ 144 | 145 | $.fn.dropdown.noConflict = function () { 146 | $.fn.dropdown = old 147 | return this 148 | } 149 | 150 | 151 | /* APPLY TO STANDARD DROPDOWN ELEMENTS 152 | * =================================== */ 153 | 154 | $(document) 155 | .on('click.dropdown.data-api touchstart.dropdown.data-api', clearMenus) 156 | .on('click.dropdown touchstart.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() }) 157 | .on('touchstart.dropdown.data-api', '.dropdown-menu', function (e) { e.stopPropagation() }) 158 | .on('click.dropdown.data-api touchstart.dropdown.data-api' , toggle, Dropdown.prototype.toggle) 159 | .on('keydown.dropdown.data-api touchstart.dropdown.data-api', toggle + ', [role=menu]' , Dropdown.prototype.keydown) 160 | 161 | }(window.jQuery); -------------------------------------------------------------------------------- /www/resources/bootstrap/js/bootstrap-popover.js: -------------------------------------------------------------------------------- 1 | /* =========================================================== 2 | * bootstrap-popover.js v2.2.2 3 | * http://twitter.github.com/bootstrap/javascript.html#popovers 4 | * =========================================================== 5 | * Copyright 2012 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * =========================================================== */ 19 | 20 | 21 | !function ($) { 22 | 23 | "use strict"; // jshint ;_; 24 | 25 | 26 | /* POPOVER PUBLIC CLASS DEFINITION 27 | * =============================== */ 28 | 29 | var Popover = function (element, options) { 30 | this.init('popover', element, options) 31 | } 32 | 33 | 34 | /* NOTE: POPOVER EXTENDS BOOTSTRAP-TOOLTIP.js 35 | ========================================== */ 36 | 37 | Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype, { 38 | 39 | constructor: Popover 40 | 41 | , setContent: function () { 42 | var $tip = this.tip() 43 | , title = this.getTitle() 44 | , content = this.getContent() 45 | 46 | $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title) 47 | $tip.find('.popover-content')[this.options.html ? 'html' : 'text'](content) 48 | 49 | $tip.removeClass('fade top bottom left right in') 50 | } 51 | 52 | , hasContent: function () { 53 | return this.getTitle() || this.getContent() 54 | } 55 | 56 | , getContent: function () { 57 | var content 58 | , $e = this.$element 59 | , o = this.options 60 | 61 | content = $e.attr('data-content') 62 | || (typeof o.content == 'function' ? o.content.call($e[0]) : o.content) 63 | 64 | return content 65 | } 66 | 67 | , tip: function () { 68 | if (!this.$tip) { 69 | this.$tip = $(this.options.template) 70 | } 71 | return this.$tip 72 | } 73 | 74 | , destroy: function () { 75 | this.hide().$element.off('.' + this.type).removeData(this.type) 76 | } 77 | 78 | }) 79 | 80 | 81 | /* POPOVER PLUGIN DEFINITION 82 | * ======================= */ 83 | 84 | var old = $.fn.popover 85 | 86 | $.fn.popover = function (option) { 87 | return this.each(function () { 88 | var $this = $(this) 89 | , data = $this.data('popover') 90 | , options = typeof option == 'object' && option 91 | if (!data) $this.data('popover', (data = new Popover(this, options))) 92 | if (typeof option == 'string') data[option]() 93 | }) 94 | } 95 | 96 | $.fn.popover.Constructor = Popover 97 | 98 | $.fn.popover.defaults = $.extend({} , $.fn.tooltip.defaults, { 99 | placement: 'right' 100 | , trigger: 'click' 101 | , content: '' 102 | , template: '

' 103 | }) 104 | 105 | 106 | /* POPOVER NO CONFLICT 107 | * =================== */ 108 | 109 | $.fn.popover.noConflict = function () { 110 | $.fn.popover = old 111 | return this 112 | } 113 | 114 | }(window.jQuery); -------------------------------------------------------------------------------- /www/resources/bootstrap/js/bootstrap-scrollspy.js: -------------------------------------------------------------------------------- 1 | /* ============================================================= 2 | * bootstrap-scrollspy.js v2.2.2 3 | * http://twitter.github.com/bootstrap/javascript.html#scrollspy 4 | * ============================================================= 5 | * Copyright 2012 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ============================================================== */ 19 | 20 | 21 | !function ($) { 22 | 23 | "use strict"; // jshint ;_; 24 | 25 | 26 | /* SCROLLSPY CLASS DEFINITION 27 | * ========================== */ 28 | 29 | function ScrollSpy(element, options) { 30 | var process = $.proxy(this.process, this) 31 | , $element = $(element).is('body') ? $(window) : $(element) 32 | , href 33 | this.options = $.extend({}, $.fn.scrollspy.defaults, options) 34 | this.$scrollElement = $element.on('scroll.scroll-spy.data-api', process) 35 | this.selector = (this.options.target 36 | || ((href = $(element).attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7 37 | || '') + ' .nav li > a' 38 | this.$body = $('body') 39 | this.refresh() 40 | this.process() 41 | } 42 | 43 | ScrollSpy.prototype = { 44 | 45 | constructor: ScrollSpy 46 | 47 | , refresh: function () { 48 | var self = this 49 | , $targets 50 | 51 | this.offsets = $([]) 52 | this.targets = $([]) 53 | 54 | $targets = this.$body 55 | .find(this.selector) 56 | .map(function () { 57 | var $el = $(this) 58 | , href = $el.data('target') || $el.attr('href') 59 | , $href = /^#\w/.test(href) && $(href) 60 | return ( $href 61 | && $href.length 62 | && [[ $href.position().top + self.$scrollElement.scrollTop(), href ]] ) || null 63 | }) 64 | .sort(function (a, b) { return a[0] - b[0] }) 65 | .each(function () { 66 | self.offsets.push(this[0]) 67 | self.targets.push(this[1]) 68 | }) 69 | } 70 | 71 | , process: function () { 72 | var scrollTop = this.$scrollElement.scrollTop() + this.options.offset 73 | , scrollHeight = this.$scrollElement[0].scrollHeight || this.$body[0].scrollHeight 74 | , maxScroll = scrollHeight - this.$scrollElement.height() 75 | , offsets = this.offsets 76 | , targets = this.targets 77 | , activeTarget = this.activeTarget 78 | , i 79 | 80 | if (scrollTop >= maxScroll) { 81 | return activeTarget != (i = targets.last()[0]) 82 | && this.activate ( i ) 83 | } 84 | 85 | for (i = offsets.length; i--;) { 86 | activeTarget != targets[i] 87 | && scrollTop >= offsets[i] 88 | && (!offsets[i + 1] || scrollTop <= offsets[i + 1]) 89 | && this.activate( targets[i] ) 90 | } 91 | } 92 | 93 | , activate: function (target) { 94 | var active 95 | , selector 96 | 97 | this.activeTarget = target 98 | 99 | $(this.selector) 100 | .parent('.active') 101 | .removeClass('active') 102 | 103 | selector = this.selector 104 | + '[data-target="' + target + '"],' 105 | + this.selector + '[href="' + target + '"]' 106 | 107 | active = $(selector) 108 | .parent('li') 109 | .addClass('active') 110 | 111 | if (active.parent('.dropdown-menu').length) { 112 | active = active.closest('li.dropdown').addClass('active') 113 | } 114 | 115 | active.trigger('activate') 116 | } 117 | 118 | } 119 | 120 | 121 | /* SCROLLSPY PLUGIN DEFINITION 122 | * =========================== */ 123 | 124 | var old = $.fn.scrollspy 125 | 126 | $.fn.scrollspy = function (option) { 127 | return this.each(function () { 128 | var $this = $(this) 129 | , data = $this.data('scrollspy') 130 | , options = typeof option == 'object' && option 131 | if (!data) $this.data('scrollspy', (data = new ScrollSpy(this, options))) 132 | if (typeof option == 'string') data[option]() 133 | }) 134 | } 135 | 136 | $.fn.scrollspy.Constructor = ScrollSpy 137 | 138 | $.fn.scrollspy.defaults = { 139 | offset: 10 140 | } 141 | 142 | 143 | /* SCROLLSPY NO CONFLICT 144 | * ===================== */ 145 | 146 | $.fn.scrollspy.noConflict = function () { 147 | $.fn.scrollspy = old 148 | return this 149 | } 150 | 151 | 152 | /* SCROLLSPY DATA-API 153 | * ================== */ 154 | 155 | $(window).on('load', function () { 156 | $('[data-spy="scroll"]').each(function () { 157 | var $spy = $(this) 158 | $spy.scrollspy($spy.data()) 159 | }) 160 | }) 161 | 162 | }(window.jQuery); -------------------------------------------------------------------------------- /www/resources/bootstrap/js/bootstrap-tab.js: -------------------------------------------------------------------------------- 1 | /* ======================================================== 2 | * bootstrap-tab.js v2.2.2 3 | * http://twitter.github.com/bootstrap/javascript.html#tabs 4 | * ======================================================== 5 | * Copyright 2012 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ======================================================== */ 19 | 20 | 21 | !function ($) { 22 | 23 | "use strict"; // jshint ;_; 24 | 25 | 26 | /* TAB CLASS DEFINITION 27 | * ==================== */ 28 | 29 | var Tab = function (element) { 30 | this.element = $(element) 31 | } 32 | 33 | Tab.prototype = { 34 | 35 | constructor: Tab 36 | 37 | , show: function () { 38 | var $this = this.element 39 | , $ul = $this.closest('ul:not(.dropdown-menu)') 40 | , selector = $this.attr('data-target') 41 | , previous 42 | , $target 43 | , e 44 | 45 | if (!selector) { 46 | selector = $this.attr('href') 47 | selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 48 | } 49 | 50 | if ( $this.parent('li').hasClass('active') ) return 51 | 52 | previous = $ul.find('.active:last a')[0] 53 | 54 | e = $.Event('show', { 55 | relatedTarget: previous 56 | }) 57 | 58 | $this.trigger(e) 59 | 60 | if (e.isDefaultPrevented()) return 61 | 62 | $target = $(selector) 63 | 64 | this.activate($this.parent('li'), $ul) 65 | this.activate($target, $target.parent(), function () { 66 | $this.trigger({ 67 | type: 'shown' 68 | , relatedTarget: previous 69 | }) 70 | }) 71 | } 72 | 73 | , activate: function ( element, container, callback) { 74 | var $active = container.find('> .active') 75 | , transition = callback 76 | && $.support.transition 77 | && $active.hasClass('fade') 78 | 79 | function next() { 80 | $active 81 | .removeClass('active') 82 | .find('> .dropdown-menu > .active') 83 | .removeClass('active') 84 | 85 | element.addClass('active') 86 | 87 | if (transition) { 88 | element[0].offsetWidth // reflow for transition 89 | element.addClass('in') 90 | } else { 91 | element.removeClass('fade') 92 | } 93 | 94 | if ( element.parent('.dropdown-menu') ) { 95 | element.closest('li.dropdown').addClass('active') 96 | } 97 | 98 | callback && callback() 99 | } 100 | 101 | transition ? 102 | $active.one($.support.transition.end, next) : 103 | next() 104 | 105 | $active.removeClass('in') 106 | } 107 | } 108 | 109 | 110 | /* TAB PLUGIN DEFINITION 111 | * ===================== */ 112 | 113 | var old = $.fn.tab 114 | 115 | $.fn.tab = function ( option ) { 116 | return this.each(function () { 117 | var $this = $(this) 118 | , data = $this.data('tab') 119 | if (!data) $this.data('tab', (data = new Tab(this))) 120 | if (typeof option == 'string') data[option]() 121 | }) 122 | } 123 | 124 | $.fn.tab.Constructor = Tab 125 | 126 | 127 | /* TAB NO CONFLICT 128 | * =============== */ 129 | 130 | $.fn.tab.noConflict = function () { 131 | $.fn.tab = old 132 | return this 133 | } 134 | 135 | 136 | /* TAB DATA-API 137 | * ============ */ 138 | 139 | $(document).on('click.tab.data-api', '[data-toggle="tab"], [data-toggle="pill"]', function (e) { 140 | e.preventDefault() 141 | $(this).tab('show') 142 | }) 143 | 144 | }(window.jQuery); -------------------------------------------------------------------------------- /www/resources/bootstrap/js/bootstrap-transition.js: -------------------------------------------------------------------------------- 1 | /* =================================================== 2 | * bootstrap-transition.js v2.2.2 3 | * http://twitter.github.com/bootstrap/javascript.html#transitions 4 | * =================================================== 5 | * Copyright 2012 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ========================================================== */ 19 | 20 | 21 | !function ($) { 22 | 23 | "use strict"; // jshint ;_; 24 | 25 | 26 | /* CSS TRANSITION SUPPORT (http://www.modernizr.com/) 27 | * ======================================================= */ 28 | 29 | $(function () { 30 | 31 | $.support.transition = (function () { 32 | 33 | var transitionEnd = (function () { 34 | 35 | var el = document.createElement('bootstrap') 36 | , transEndEventNames = { 37 | 'WebkitTransition' : 'webkitTransitionEnd' 38 | , 'MozTransition' : 'transitionend' 39 | , 'OTransition' : 'oTransitionEnd otransitionend' 40 | , 'transition' : 'transitionend' 41 | } 42 | , name 43 | 44 | for (name in transEndEventNames){ 45 | if (el.style[name] !== undefined) { 46 | return transEndEventNames[name] 47 | } 48 | } 49 | 50 | }()) 51 | 52 | return transitionEnd && { 53 | end: transitionEnd 54 | } 55 | 56 | })() 57 | 58 | }) 59 | 60 | }(window.jQuery); -------------------------------------------------------------------------------- /www/resources/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damicon/zfswatcher/50e5382719068d6dc20b256f0021183560697ea7/www/resources/favicon.ico -------------------------------------------------------------------------------- /www/resources/zfswatcher.css: -------------------------------------------------------------------------------- 1 | .table-zfswatcher-dashboard th, 2 | .table-zfswatcher-dashboard td { 3 | vertical-align:middle; 4 | } 5 | 6 | .progress-zfswatcher-dashboard { 7 | margin-bottom: 0px; 8 | min-width: 50px; 9 | } 10 | -------------------------------------------------------------------------------- /www/templates/dashboard.html: -------------------------------------------------------------------------------- 1 | {{ template "header.html" .Nav }} 2 | 3 | {{ with .Data }} 4 |
5 |
load averages:
{{ index .SysLoadaverage 0 | printf "%.2f" }}, {{ index .SysLoadaverage 1 | printf "%.2f" }}, {{ index .SysLoadaverage 2 | printf "%.2f" }} 6 |
system uptime:
{{ .SysUptime }}
7 |
zfswatcher uptime:
{{ .ZfswatcherUptime }}
8 |
9 | {{ if .Pools }} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | {{ range .Pools }} 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 44 | 45 | 46 | {{ end }} 47 | 48 |
PoolStateTotalUsed%Avail%
{{ .Name }}{{ .State }}{{ nicenumber .Total }}{{ nicenumber .Used }}{{ .UsedPercent }}%{{ nicenumber .Avail }}{{ .AvailPercent }}% 35 | 36 |
37 | {{ if or .UsedPercent .AvailPercent }} 38 |
39 |
40 | {{ end }} 41 |
42 |
43 |
49 | {{ else }} 50 |
51 | There are no pools. 52 |
53 | {{ end }} 54 | {{ end }} 55 | 56 | {{ template "footer.html" .Nav }} 57 | -------------------------------------------------------------------------------- /www/templates/footer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /www/templates/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | zfswatcher 6 | 7 | 8 | 14 | 15 | 16 | 17 | 18 | 21 | 22 | 23 | 24 | 25 | 53 | 54 |
55 |
56 | -------------------------------------------------------------------------------- /www/templates/logs.html: -------------------------------------------------------------------------------- 1 | {{ template "header.html" .Nav }} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | {{ range $n, $d := .Data }} 13 | 14 | 15 | 16 | 30 | 31 | {{ end }} 32 | 33 |
TimeSeverityMessage
{{ $d.Time }}{{ $d.Severity }}{{ $d.Text }} 17 | {{ if $d.Attachment }} 18 | 24 |
26 |
{{ $d.Attachment }}
27 |
28 | {{ end }} 29 |
34 | 35 | {{ template "footer.html" .Nav }} 36 | -------------------------------------------------------------------------------- /www/templates/statistics.html: -------------------------------------------------------------------------------- 1 | {{ template "header.html" .Nav }} 2 | 3 |
4 | Statistics not yet implemented. 5 | There will be some graphs in the next version. 6 |
7 | 8 | {{ template "footer.html" .Nav }} 9 | -------------------------------------------------------------------------------- /www/templates/status-many.html: -------------------------------------------------------------------------------- 1 | {{ template "header.html" .Nav }} 2 | 3 | 10 | 11 | {{ template "status-pool.html" .Data }} 12 | 13 | {{ template "footer.html" .Nav }} 14 | -------------------------------------------------------------------------------- /www/templates/status-none.html: -------------------------------------------------------------------------------- 1 | {{ template "header.html" .Nav }} 2 | 3 |
4 | There are no pools. 5 |
6 | 7 | {{ template "footer.html" .Nav }} 8 | -------------------------------------------------------------------------------- /www/templates/status-pool.html: -------------------------------------------------------------------------------- 1 |
2 |
name:
{{ .Name }}
3 |
state:
{{ .State }}
4 | {{ if .Status }} 5 |
status:
{{ .Status }}
6 | {{ end }} 7 | {{ if .Action }} 8 |
action:
{{ .Action }}
9 | {{ end }} 10 | {{ if .See }} 11 |
see:
{{ .See }}
12 | {{ end }} 13 | {{ if .Scan }} 14 |
scan:
{{ .Scan }}
15 | {{ end }} 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | {{ range .Devs }} 31 | 32 | 33 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | {{ end }} 59 | 60 |
NameLocStateReadWriteCksum
{{ .Name }} 34 | {{ if .EnableLed }} 35 |
36 | {{ if .Locate }} 37 | 38 | 39 | 42 | {{ else }} 43 | 44 | 45 | 48 | {{ end }} 49 |
50 | {{ end }} 51 |
{{ .State }}{{ nicenumber .Read }}{{ nicenumber .Write }}{{ nicenumber .Cksum }}{{ .Rest }}
61 | 62 |
63 | {{ if .Errors }} 64 |
errors:
{{ .Errors }}
65 | {{ end }} 66 |
67 | -------------------------------------------------------------------------------- /www/templates/status-single.html: -------------------------------------------------------------------------------- 1 | {{ template "header.html" .Nav }} 2 | 3 | {{ template "status-pool.html" .Data }} 4 | 5 | {{ template "footer.html" .Nav }} 6 | -------------------------------------------------------------------------------- /www/templates/usage.html: -------------------------------------------------------------------------------- 1 | {{ template "header.html" .Nav }} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | {{ range .Data }} 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | {{ end }} 34 | 35 |
NameAvailUsedUsed byReferMount point
snapdatasetrefreservationchild
{{ .Name }}{{ nicenumber .Avail }}{{ nicenumber .Used }}{{ nicenumber .Usedsnap }}{{ nicenumber .Usedds }}{{ nicenumber .Usedrefreserv }}{{ nicenumber .Usedchild }}{{ nicenumber .Refer }}{{ .Mountpoint }}
36 | 37 | {{ template "footer.html" .Nav }} 38 | -------------------------------------------------------------------------------- /zfswatcher.spec: -------------------------------------------------------------------------------- 1 | Name: zfswatcher 2 | Version: %{version} 3 | Release: 1%{?dist} 4 | Summary: ZFS pool monitoring and notification daemon 5 | 6 | Group: Applications/System 7 | License: GPLv3+ 8 | Vendor: Damicon Kraa Oy 9 | Packager: Janne Snabb 10 | URL: http://zfswatcher.damicon.fi/ 11 | Source0: %{name}-%{version}.tar.gz 12 | ExclusiveArch: x86_64 13 | BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) 14 | 15 | #BuildRequires: # Go 1.0.3 16 | Requires: zfs 17 | Requires(post): chkconfig 18 | Requires(preun): chkconfig 19 | Requires(preun): initscripts 20 | Requires(postun): initscripts 21 | 22 | %description 23 | Zfswatcher is ZFS pool monitoring and notification daemon 24 | with the following main features: 25 | * Periodically inspects the zpool status. 26 | * Sends configurable notifications on status changes. 27 | * Controls the disk enclosure LEDs. 28 | * Web interface for displaying status and logs. 29 | 30 | %prep 31 | %setup -q 32 | 33 | 34 | %build 35 | make 36 | 37 | 38 | %install 39 | rm -rf $RPM_BUILD_ROOT 40 | make install DESTDIR=$RPM_BUILD_ROOT 41 | 42 | %__mkdir_p ${RPM_BUILD_ROOT}%{_initddir} 43 | %__install -p -m 755 etc/redhat-startup.sh ${RPM_BUILD_ROOT}%{_initddir}/%{name} 44 | 45 | %__mkdir_p -m 755 ${RPM_BUILD_ROOT}%{_sysconfdir}/logrotate.d 46 | %__install -p -m 644 etc/logrotate.conf ${RPM_BUILD_ROOT}%{_sysconfdir}/logrotate.d/%{name} 47 | 48 | 49 | %clean 50 | rm -rf $RPM_BUILD_ROOT 51 | 52 | 53 | %files 54 | %defattr(-,root,root,-) 55 | %doc README.md COPYING NEWS 56 | %{_sbindir}/* 57 | %{_mandir}/man8/* 58 | %{_datadir}/%{name}/ 59 | %{_initddir}/%{name} 60 | %config(noreplace) %{_sysconfdir}/zfs/*.conf 61 | %config(noreplace) %{_sysconfdir}/logrotate.d/* 62 | 63 | 64 | %post 65 | # This adds the proper /etc/rc*.d links for the script 66 | /sbin/chkconfig --add zfswatcher 67 | 68 | 69 | %preun 70 | if [ $1 -eq 0 ] ; then 71 | /sbin/service zfswatcher stop >/dev/null 2>&1 72 | /sbin/chkconfig --del zfswatcher 73 | fi 74 | 75 | 76 | %postun 77 | if [ "$1" -ge "1" ] ; then 78 | /sbin/service zfswatcher condrestart >/dev/null 2>&1 || : 79 | fi 80 | 81 | 82 | --------------------------------------------------------------------------------