├── .bashrc ├── .gitignore ├── Makefile ├── PKGBUILD ├── README.md ├── go-luks-suspend.service ├── initcpio-hook ├── install ├── src └── goLuksSuspend │ ├── cmd │ ├── go-luks-suspend │ │ ├── exec.go │ │ ├── filesystem.go │ │ └── main.go │ └── initramfs-suspend │ │ ├── exec.go │ │ └── main.go │ ├── cryptdevice.go │ ├── cryptdevice_test.go │ ├── keyfile.go │ ├── keyfile_test.go │ ├── lib.go │ └── version.go └── vendor ├── manifest └── src └── github.com └── guns └── golibs ├── editreader ├── editreader.go └── editreader_test.go ├── errutil ├── errutil.go └── errutil_test.go ├── process ├── terminate.go └── terminate_test.go └── sys ├── sys.go └── tty.go /.bashrc: -------------------------------------------------------------------------------- 1 | pushd . &>/dev/null 2 | 3 | cd "$(dirname "${BASH_SOURCE[0]}")" 4 | 5 | if [[ "$GOPATH" != "$PWD":* ]]; then 6 | export GOPATH="$PWD:$GOPATH" 7 | echo "GOPATH=$GOPATH" 8 | fi 9 | 10 | popd &>/dev/null 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | /pkg/ 3 | /*.pkg.* 4 | /go-luks-suspend 5 | /initramfs-suspend 6 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all install 2 | 3 | INSTALL_DIR := /usr/lib/go-luks-suspend 4 | GOPATH := "$(CURDIR):$(CURDIR)/vendor" 5 | 6 | all: go-luks-suspend initramfs-suspend 7 | 8 | update-version: 9 | ifdef VERSION 10 | /usr/bin/sed -i "s/^const Version = .*/const Version = \"$(VERSION)\"/" src/goLuksSuspend/version.go 11 | endif 12 | 13 | go-luks-suspend: update-version 14 | GOPATH=$(GOPATH) go build goLuksSuspend/cmd/go-luks-suspend 15 | 16 | initramfs-suspend: update-version 17 | GOPATH=$(GOPATH) go build goLuksSuspend/cmd/initramfs-suspend 18 | 19 | install: all 20 | install -Dm755 go-luks-suspend "$(DESTDIR)$(INSTALL_DIR)/go-luks-suspend" 21 | install -Dm755 initramfs-suspend "$(DESTDIR)$(INSTALL_DIR)/initramfs-suspend" 22 | install -Dm644 initcpio-hook "$(DESTDIR)/usr/lib/initcpio/install/suspend" 23 | install -Dm644 go-luks-suspend.service "$(DESTDIR)/usr/lib/systemd/system/go-luks-suspend.service" 24 | 25 | clean: 26 | rm -f go-luks-suspend initramfs-suspend 27 | 28 | # vim:set sw=4 ts=4 noet: 29 | -------------------------------------------------------------------------------- /PKGBUILD: -------------------------------------------------------------------------------- 1 | # Maintainer: guns 2 | # Contributor: Vianney le Clément de Saint-Marcq 3 | pkgname=go-luks-suspend 4 | pkgver=1.4.3 5 | pkgrel=1 6 | pkgdesc='Encrypt LUKS volumes on system suspend' 7 | arch=('x86_64') 8 | url='https://github.com/guns/go-luks-suspend' 9 | license=('GPL3') 10 | depends=('systemd' 'cryptsetup' 'mkinitcpio') 11 | makedepends=('go') 12 | install=install 13 | conflicts=('arch-luks-suspend' 'arch-luks-suspend-git') 14 | 15 | package() { 16 | cd "$startdir" 17 | make clean 18 | make DESTDIR="$pkgdir/" VERSION="v$pkgver" install 19 | } 20 | 21 | # vim:set ts=2 sw=2 et: 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | go-luks-suspend 2 | =============== 3 | 4 | A package for [Arch Linux][] to lock LUKS encrypted volumes on suspend. 5 | 6 | When using [dm-crypt with LUKS][] to set up full system encryption, the 7 | encryption key is kept in memory when suspending the system. This drawback 8 | defeats the purpose of encryption if you are ever physically separated from 9 | your machine. One can use the `cryptsetup luksSuspend` command to freeze all 10 | I/O and flush the key from memory, but special care must be taken when 11 | applying it to the root device. 12 | 13 | The `go-luks-suspend` program replaces the default suspend mechanism of 14 | systemd. It chroots to initramfs in order to perform the `luksSuspend`, 15 | suspend to RAM, and `luksResume` operations. It relies on the `shutdown` 16 | initcpio hook to provide access to the initramfs. 17 | 18 | This project is a rewrite of Vianney le Clément's excellent project 19 | [arch-luks-suspend][] in the Go programming language, and features the 20 | following improvements: 21 | 22 | - All non-root LUKS volumes are locked on suspend. 23 | 24 | - Root LUKS volumes can be unlocked with a keyfile. (Press `CTRL-R` at the 25 | prompt to unlock the root volume with a keyfile stored on a removable 26 | device. See [`cryptkey`][cryptkey].) 27 | 28 | - Non-root LUKS volumes with keyfiles specified in `/etc/crypttab` are 29 | concurrently unlocked on wake. 30 | 31 | - Press `Escape` to re-suspend the system after wake without having to unlock 32 | it first. ([N.B.][escape]) 33 | 34 | [Arch Linux]: https://www.archlinux.org/ 35 | [dm-crypt with LUKS]: https://wiki.archlinux.org/index.php/Dm-crypt_with_LUKS 36 | [arch-luks-suspend]: https://github.com/vianney/arch-luks-suspend 37 | [cryptkey]: https://wiki.archlinux.org/index.php/Dm-crypt/System_configuration#cryptkey 38 | [escape]: https://github.com/guns/go-luks-suspend#q-my-system-doesnt-re-suspend-with-the-escape-key-after-wake-but-before-unlock 39 | 40 | 41 | Installation 42 | ------------ 43 | 44 | 1. Install this AUR package: https://aur.archlinux.org/packages/go-luks-suspend/
45 | Alternatively, run `make install` as root. 46 | 47 | 2. Edit `/etc/mkinitcpio.conf` and make sure the following hooks are enabled:
48 | `udev`, `encrypt`, `shutdown`, and `suspend`. 49 | 50 | 3. Rebuild the initramfs: `mkinitcpio -p linux`. 51 | 52 | 4. Enable the service: `systemctl enable go-luks-suspend.service` 53 | 54 | 5. Reboot. 55 | 56 | 57 | Q. How do I unlock non-root LUKS volumes on wake? 58 | ------------------------------------------------- 59 | 60 | A. `go-luks-suspend` locks all active LUKS volumes on the system, but will 61 | only prompt the user to unlock the root volume on wake. 62 | 63 | To unlock a non-root LUKS volume on wake, add an entry with a keyfile in 64 | `/etc/crypttab`: 65 | 66 | ```ini 67 | # /etc/crypttab 68 | # 69 | # 70 | crypt-01 UUID=51932da0-6da1-4e92-9c2e-fc0063b2fcdb /root/crypt-01.key luks 71 | crypt-02 UUID=4bf96ca0-8d10-47e9-bf57-aea2c72a472d /root/crypt-02.key luks 72 | crypt-03 UUID=7a790264-34a3-40d7-837f-b76271710e2a /root/crypt-03.key luks 73 | ``` 74 | 75 | In the example above, `crypt-01`, `crypt-02`, and `crypt-03` will be unlocked 76 | concurrently on wake after the user successfully unlocks the root volume with 77 | a passphrase. 78 | 79 | 80 | Q. How do I poweroff the system on errors? 81 | ------------------------------------------ 82 | 83 | A. The `-poweroff` flag instructs `go-luks-suspend` to power off the machine 84 | on error or when the user fails to unlock the root volume on wake. To add this 85 | flag to the `go-luks-suspend` command line: 86 | 87 | 1. Override the service file: 88 | 89 | ``` 90 | # systemctl edit go-luks-suspend.service 91 | ``` 92 | 93 | 2. Redefine the `ExecStart` entry with the `-poweroff` flag: 94 | 95 | ```ini 96 | [Service] 97 | ExecStart= 98 | ExecStart=/usr/bin/openvt -ws -- /usr/lib/go-luks-suspend/go-luks-suspend -poweroff 99 | ``` 100 | 101 | 102 | Q. My system doesn't re-suspend with the Escape key after wake but before unlock! 103 | --------------------------------------------------------------------------------- 104 | 105 | A. The kernel calls [`thaw_processes()`][thaw] after waking the system from 106 | suspend. This wakes up all processes on the system, any of which may initiate 107 | IO with a locked LUKS volume. 108 | 109 | These processes, in turn, refuse to be frozen by `freeze_processes()`, which 110 | is called during the system suspend sequence. Because the kernel refuses to 111 | suspend the system until the hanging processes are frozen, the only way to 112 | re-suspend the system at this point is unlock the affected LUKS volume, let 113 | the IO complete, and try again. 114 | 115 | In practice, network IO after wake is the largest reason that suspend fails 116 | after-wake-but-before-unlock. It is therefore recommended that you bring down 117 | the machine's network interfaces before suspend and restore them on wake. 118 | 119 | [thaw]: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/power/freezing-of-tasks.txt 120 | 121 | 122 | Q. How do I run go-luks-suspend in debug mode? 123 | ---------------------------------------------- 124 | 125 | A. Run `go-luks-suspend` with the `-debug` flag to print debugging messages 126 | and to spawn a rescue shell on errors. 127 | 128 | ``` 129 | # /usr/lib/go-luks-suspend/go-luks-suspend -debug 130 | ``` 131 | 132 | 133 | Authors and license 134 | ------------------- 135 | 136 | Copyright 2017 Sung Pae (Go implementation) 137 | 138 | Copyright 2013 Vianney le Clément de Saint-Marcq 139 | 140 | This program is free software: you can redistribute it and/or modify 141 | it under the terms of the GNU General Public License as published by 142 | the Free Software Foundation; version 3 of the License. 143 | 144 | This program is distributed in the hope that it will be useful, 145 | but WITHOUT ANY WARRANTY; without even the implied warranty of 146 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 147 | GNU General Public License for more details. 148 | 149 | You should have received a copy of the GNU General Public License 150 | along with This program. If not, see . 151 | -------------------------------------------------------------------------------- /go-luks-suspend.service: -------------------------------------------------------------------------------- 1 | # NOTE: This file masks /usr/lib/systemd/system/systemd-suspend.service 2 | # 3 | # This file has been adapted from systemd. 4 | # 5 | # systemd is free software; you can redistribute it and/or modify it 6 | # under the terms of the GNU Lesser General Public License as published by 7 | # the Free Software Foundation; either version 2.1 of the License, or 8 | # (at your option) any later version. 9 | 10 | [Unit] 11 | Description=Suspend 12 | Documentation=man:systemd-suspend.service(8) 13 | DefaultDependencies=no 14 | Requires=sleep.target 15 | After=sleep.target 16 | 17 | [Install] 18 | Alias=systemd-suspend.service 19 | 20 | [Service] 21 | Type=oneshot 22 | ExecStart=/usr/bin/openvt -ws -- /usr/lib/go-luks-suspend/go-luks-suspend 23 | -------------------------------------------------------------------------------- /initcpio-hook: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | build() { 4 | add_file "/usr/lib/go-luks-suspend/initramfs-suspend" "/suspend" 755 5 | } 6 | 7 | help() { 8 | cat < cryptdevices. 27 | for i := len(cryptdevs) - 1; i >= 0; i-- { 28 | if err := g.Cryptsetup("luksSuspend", cryptdevs[i].Name); err != nil { 29 | return err 30 | } 31 | } 32 | 33 | return nil 34 | } 35 | 36 | func startUdevDaemon() error { 37 | return g.Run(exec.Command("/usr/lib/systemd/systemd-udevd", "--daemon", "--resolve-names=never")) 38 | } 39 | 40 | func stopUdevDaemon() error { 41 | return g.Run(exec.Command("/usr/bin/udevadm", "control", "--exit")) 42 | } 43 | 44 | func printPassphrasePrompt(rootdev *g.Cryptdevice) { 45 | fmt.Print("\nPress Escape to suspend to RAM") 46 | if rootdev.Keyfile.Defined() { 47 | fmt.Print(", or Ctrl-R to rescan block devices for keyfiles") 48 | } 49 | if g.DebugMode { 50 | fmt.Print(", or Ctrl-T to start a debug shell") 51 | } 52 | fmt.Println(".") 53 | fmt.Printf("\nEnter passphrase for %s: ", rootdev.Name) 54 | } 55 | 56 | func luksResume(cd *g.Cryptdevice, stdin io.Reader) error { 57 | if cd.Keyfile.Defined() { 58 | if cd.Keyfile.Available() { 59 | fmt.Printf("Attempting to unlock %s with keyfile...\n", cd.Name) 60 | if err := cd.ResumeWithKeyfile(); err == nil { 61 | return nil 62 | } 63 | } else { 64 | fmt.Println("Keyfile unavailable.") 65 | } 66 | } 67 | 68 | printPassphrasePrompt(cd) 69 | return cd.Resume(stdin) 70 | } 71 | 72 | func resumeRootCryptdevice(rootdev *g.Cryptdevice) error { 73 | restoreTTY, err := sys.AlterTTY(os.Stdin.Fd(), sys.TCSETSF, func(tty *syscall.Termios) { 74 | tty.Lflag &^= syscall.ICANON | syscall.ECHO 75 | }) 76 | 77 | ttyRestored := false 78 | 79 | if restoreTTY != nil { 80 | defer func() { 81 | if !ttyRestored { 82 | g.Assert(restoreTTY()) 83 | } 84 | }() 85 | } 86 | 87 | if err != nil { 88 | g.Warn(err.Error()) 89 | return luksResume(rootdev, os.Stdin) 90 | } 91 | 92 | // The `secure` parameter to editreader.New zeroes memory aggressively 93 | r := editreader.New(os.Stdin, 4096, true, func(i int, b byte) editreader.Op { 94 | switch b { 95 | case 0x1b: // ^[ 96 | g.Debug("suspending to RAM") 97 | g.Assert(g.SuspendToRAM()) 98 | fmt.Println() 99 | printPassphrasePrompt(rootdev) 100 | return editreader.Kill 101 | case 0x17: // ^W 102 | return editreader.Kill 103 | case '\n': 104 | fmt.Println() 105 | g.Assert(restoreTTY()) 106 | ttyRestored = true 107 | return editreader.Append | editreader.Flush | editreader.Close 108 | case 0x03: // ^C 109 | fmt.Println() 110 | return editreader.Kill | editreader.Flush | editreader.Close 111 | case 0x12: // ^R 112 | if rootdev.Keyfile.Defined() { 113 | fmt.Println() 114 | return editreader.Kill | editreader.Flush | editreader.Close 115 | } 116 | return editreader.BasicLineEdit(i, b) 117 | case 0x14: // ^T 118 | if g.DebugMode { 119 | fmt.Println() 120 | g.DebugShell() 121 | printPassphrasePrompt(rootdev) 122 | return editreader.Kill 123 | } 124 | fallthrough 125 | default: 126 | return editreader.BasicLineEdit(i, b) 127 | } 128 | }) 129 | 130 | return luksResume(rootdev, r) 131 | } 132 | -------------------------------------------------------------------------------- /src/goLuksSuspend/cmd/initramfs-suspend/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | g "goLuksSuspend" 7 | ) 8 | 9 | func main() { 10 | g.ParseFlags() 11 | 12 | g.Debug("loading cryptdevices") 13 | r := os.NewFile(uintptr(3), "r") 14 | cryptdevs, err := loadCryptdevices(r) 15 | g.Assert(err) 16 | g.Assert(r.Close()) 17 | 18 | if len(cryptdevs) == 0 { 19 | // This branch should be impossible. 20 | g.Warn("no cryptdevices found, doing normal suspend") 21 | g.Assert(g.SuspendToRAM()) 22 | return 23 | } 24 | 25 | if cryptdevs[0].Keyfile.Defined() { 26 | g.Debug("starting udevd from initramfs") 27 | g.Assert(startUdevDaemon()) 28 | 29 | defer func() { 30 | g.Debug("stopping udevd within initramfs") 31 | g.Assert(stopUdevDaemon()) 32 | }() 33 | } 34 | 35 | g.Debug("suspending cryptdevices") 36 | g.Assert(suspendCryptdevices(cryptdevs)) 37 | 38 | // Crypt keys have been purged, so be less paranoid 39 | g.IgnoreErrors = true 40 | 41 | // Shorten task freeze timeout 42 | oldtimeout, err := g.SetFreezeTimeout([]byte{'1', '0', '0', '0'}) 43 | if err == nil { 44 | defer func() { 45 | _, e := g.SetFreezeTimeout(oldtimeout) 46 | g.Assert(e) 47 | }() 48 | } else { 49 | g.Assert(err) 50 | } 51 | 52 | if g.DebugMode { 53 | g.Debug("debug: skipping suspend to RAM") 54 | } else { 55 | g.Assert(g.SuspendToRAM()) 56 | } 57 | 58 | g.Debug("resuming root cryptdevice") 59 | for { 60 | var err error 61 | for i := 0; i < 3; i++ { 62 | err = resumeRootCryptdevice(&cryptdevs[0]) 63 | if err == nil { 64 | return 65 | } 66 | } 67 | // The -poweroff flag indicates the user's desire to take the 68 | // system offline on failure to unlock. 69 | if g.PoweroffOnError { 70 | g.IgnoreErrors = false 71 | g.Assert(err) 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/goLuksSuspend/cryptdevice.go: -------------------------------------------------------------------------------- 1 | package goLuksSuspend 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "errors" 7 | "fmt" 8 | "io" 9 | "io/ioutil" 10 | "os" 11 | "os/exec" 12 | "path/filepath" 13 | "regexp" 14 | "strconv" 15 | "strings" 16 | "syscall" 17 | 18 | "github.com/guns/golibs/errutil" 19 | ) 20 | 21 | type Cryptdevice struct { 22 | Name string 23 | uuid []byte 24 | dmdir string 25 | Keyfile Keyfile 26 | IsRootDevice bool 27 | } 28 | 29 | var luksUUIDPrefix = []byte("CRYPT-LUKS1-") 30 | 31 | func GetCryptdevices() ([]Cryptdevice, map[string]*Cryptdevice, error) { 32 | dirs, err := filepath.Glob("/sys/block/*/dm") 33 | if err != nil || len(dirs) == 0 { 34 | return nil, nil, err 35 | } 36 | 37 | rootdev, rootkey, err := parseKernelCmdline() 38 | if err != nil { 39 | return nil, nil, err 40 | } 41 | 42 | cryptdevs := make([]Cryptdevice, len(dirs)) 43 | cdmap := make(map[string]*Cryptdevice, len(dirs)) 44 | j, lastidx := 1, 0 45 | 46 | for i := range dirs { 47 | // Skip if not a LUKS device 48 | uuid, err := ioutil.ReadFile(filepath.Join(dirs[i], "uuid")) 49 | if err != nil { 50 | return nil, nil, err 51 | } else if len(uuid) < len(luksUUIDPrefix) || 52 | !bytes.Equal(uuid[:len(luksUUIDPrefix)], luksUUIDPrefix) { 53 | continue 54 | } 55 | 56 | cd := Cryptdevice{ 57 | dmdir: dirs[i], 58 | uuid: bytes.TrimSuffix(uuid, []byte{'\n'}), 59 | } 60 | 61 | // Skip if suspended 62 | if cd.Suspended() { 63 | continue 64 | } 65 | 66 | name, err := ioutil.ReadFile(filepath.Join(cd.dmdir, "name")) 67 | if err != nil { 68 | return nil, nil, err 69 | } 70 | 71 | cd.Name = string(bytes.TrimSuffix(name, []byte{'\n'})) 72 | 73 | if cd.Name == rootdev { 74 | if cryptdevs[0].IsRootDevice { 75 | return nil, nil, fmt.Errorf( 76 | "multiple root cryptdevices: %s, %s", 77 | cryptdevs[0].Name, 78 | cd.Name, 79 | ) 80 | } 81 | cd.IsRootDevice = true 82 | cd.Keyfile = rootkey 83 | cryptdevs[0] = cd 84 | lastidx = 0 85 | } else if j >= len(dirs) { 86 | return nil, nil, errors.New("no root cryptdevice") 87 | } else { 88 | cryptdevs[j] = cd 89 | lastidx = j 90 | j++ 91 | } 92 | 93 | if v, ok := cdmap[cd.Name]; ok { 94 | return nil, nil, fmt.Errorf("duplicate cryptdevice: %#v", v) 95 | } 96 | cdmap[cd.Name] = &cryptdevs[lastidx] 97 | } 98 | 99 | return cryptdevs[:j], cdmap, nil 100 | } 101 | 102 | func (cd *Cryptdevice) Exists() bool { 103 | uuid, err := ioutil.ReadFile(filepath.Join(cd.dmdir, "uuid")) 104 | if err != nil { 105 | // A read error implies this device has been removed 106 | return false 107 | } 108 | 109 | return bytes.Equal(cd.uuid, bytes.TrimSuffix(uuid, []byte{'\n'})) 110 | } 111 | 112 | func (cd *Cryptdevice) Suspended() bool { 113 | buf, err := ioutil.ReadFile(filepath.Join(cd.dmdir, "suspended")) 114 | if err != nil || len(buf) == 0 { 115 | // Ignore the error here for a cleaner API; read errors imply 116 | // that the device is gone, so technically, it's not suspended 117 | return false 118 | } 119 | 120 | return buf[0] == '1' 121 | } 122 | 123 | func (cd *Cryptdevice) Resume(stdin io.Reader) error { 124 | cmd := exec.Command("/usr/bin/cryptsetup", "--tries=1", "luksResume", cd.Name) 125 | cmd.Stdin = stdin 126 | cmd.Stdout = os.Stdout 127 | cmd.Stderr = os.Stderr 128 | return Run(cmd) 129 | } 130 | 131 | var errNoKeyfile = errors.New("no keyfile") 132 | 133 | const keyfileMountDir = "/go-luks-suspend-mnt" 134 | 135 | func (cd *Cryptdevice) ResumeWithKeyfile() (err error) { 136 | args := make([]string, 0, 12) 137 | 138 | if cd.Keyfile.needsMount() { 139 | if err = os.Mkdir(keyfileMountDir, 0700); err != nil { 140 | return err 141 | } 142 | defer func() { 143 | err = errutil.First(err, os.Remove(keyfileMountDir)) 144 | }() 145 | 146 | if err = syscall.Mount(cd.Keyfile.Device, keyfileMountDir, cd.Keyfile.FSType, syscall.MS_RDONLY, ""); err != nil { 147 | return err 148 | } 149 | defer func() { 150 | err = errutil.First(err, syscall.Unmount(keyfileMountDir, 0)) 151 | }() 152 | 153 | args = append(args, "--key-file", filepath.Join(keyfileMountDir, cd.Keyfile.Path)) 154 | } else { 155 | args = append(args, "--key-file", cd.Keyfile.Path) 156 | if cd.Keyfile.Offset > 0 { 157 | args = append(args, "--keyfile-offset", strconv.FormatUint(cd.Keyfile.Offset, 10)) 158 | } 159 | if cd.Keyfile.Size > 0 { 160 | args = append(args, "--keyfile-size", strconv.FormatUint(cd.Keyfile.Size, 10)) 161 | } 162 | if cd.Keyfile.KeySlotDefined() { 163 | args = append(args, "--key-slot", strconv.FormatUint(cd.Keyfile.GetKeySlot(), 10)) 164 | } 165 | if len(cd.Keyfile.Header) > 0 { 166 | args = append(args, "--header", cd.Keyfile.Header) 167 | } 168 | } 169 | 170 | args = append(args, "luksResume", cd.Name) 171 | 172 | return Cryptsetup(args...) 173 | } 174 | 175 | // This is a variable to facilitate testing. 176 | var kernelCmdline = "/proc/cmdline" 177 | 178 | func parseKernelCmdline() (rootdev string, key Keyfile, err error) { 179 | buf, err := ioutil.ReadFile(kernelCmdline) 180 | if err != nil { 181 | return "", Keyfile{}, err 182 | } 183 | 184 | // 185 | // https://git.archlinux.org/svntogit/packages.git/tree/trunk/encrypt_hook?h=packages/cryptsetup 186 | // 187 | 188 | params := strings.Fields(string(buf)) 189 | 190 | for i := range params { 191 | kv := strings.SplitN(params[i], "=", 2) 192 | if len(kv) < 2 { 193 | continue 194 | } 195 | 196 | switch kv[0] { 197 | case "cryptdevice": 198 | // cryptdevice=device:dmname:options 199 | fields := strings.SplitN(kv[1], ":", 3) 200 | if len(fields) < 2 { 201 | continue 202 | } 203 | 204 | rootdev = fields[1] 205 | case "cryptkey": 206 | fields := strings.SplitN(kv[1], ":", 3) 207 | if len(fields) < 2 { 208 | continue 209 | } 210 | 211 | // cryptkey=rootfs:path 212 | if len(fields) == 2 && fields[0] == "rootfs" { 213 | key.Path = fields[1] 214 | continue 215 | } 216 | 217 | if len(fields) < 3 { 218 | continue 219 | } 220 | 221 | if offset, err := strconv.ParseUint(fields[1], 10, 0); err == nil { 222 | // cryptkey=device:offset:size 223 | size, err := strconv.ParseUint(fields[2], 10, 0) 224 | if err != nil { 225 | continue // ignore malformed entry 226 | } 227 | key.Path = resolveDevice(fields[0]) 228 | key.Offset = offset 229 | key.Size = size 230 | continue 231 | } 232 | 233 | // cryptkey=device:fstype:path 234 | key.Device = resolveDevice(fields[0]) 235 | key.FSType = fields[1] 236 | key.Path = fields[2] 237 | } 238 | } 239 | 240 | if len(rootdev) == 0 { 241 | return "", Keyfile{}, errors.New("no root cryptdevice") 242 | } 243 | 244 | return rootdev, key, nil 245 | } 246 | 247 | func resolveDevice(name string) string { 248 | kv := strings.SplitN(name, "=", 2) 249 | if len(kv) < 2 { 250 | return name 251 | } 252 | 253 | switch kv[0] { 254 | // ID= and PATH= are not supported by the encrypt hook, but are provided by udev 255 | case "UUID", "LABEL", "PARTUUID", "PARTLABEL", "ID", "PATH": 256 | return filepath.Join("/dev/disk/by-"+strings.ToLower(kv[0]), kv[1]) 257 | default: 258 | return name 259 | } 260 | } 261 | 262 | var ignoreLinePattern = regexp.MustCompile(`\A\s*\z|\A\s*#`) 263 | 264 | func AddKeyfilesFromCrypttab(cdmap map[string]*Cryptdevice) error { 265 | file, err := os.Open("/etc/crypttab") 266 | if err != nil { 267 | return err 268 | } 269 | 270 | s := bufio.NewScanner(file) 271 | 272 | for s.Scan() { 273 | line := s.Bytes() 274 | if ignoreLinePattern.Match(line) { 275 | continue 276 | } 277 | 278 | name, key := parseCrypttabEntry(string(line)) 279 | if len(name) == 0 { 280 | continue 281 | } 282 | 283 | if cd, ok := cdmap[name]; ok { 284 | cd.Keyfile = key 285 | } 286 | } 287 | 288 | return file.Close() 289 | } 290 | -------------------------------------------------------------------------------- /src/goLuksSuspend/cryptdevice_test.go: -------------------------------------------------------------------------------- 1 | package goLuksSuspend 2 | 3 | import ( 4 | "errors" 5 | "io/ioutil" 6 | "os" 7 | "testing" 8 | ) 9 | 10 | func TestKernelCmdlineParsing(t *testing.T) { 11 | kernelCmdlineSave := kernelCmdline 12 | kernelCmdline = "test_kernel_cmdline" 13 | defer func() { 14 | _ = os.Remove(kernelCmdline) // errcheck: rm -f 15 | kernelCmdline = kernelCmdlineSave 16 | }() 17 | 18 | data := []struct { 19 | in, name string 20 | key Keyfile 21 | err error 22 | }{ 23 | // cryptdevice= 24 | { 25 | in: "cryptdevice=UUID=d55cc35b-e99b-44ce-be89-4c573fccfb0b:cryptroot root=/dev/mapper/cryptroot\n", 26 | name: "cryptroot", 27 | }, 28 | { 29 | in: "cryptdevice=/dev/sda1:cryptroot1 cryptdevice=/dev/sda2:cryptroot2\n", 30 | name: "cryptroot2", 31 | }, 32 | { 33 | in: "cryptdevice=UUID=cd5dd4dc-5766-493e-b3c6-3d6dfd195082:cryptolvm:allow-discards root=/dev/mapper/system-root", 34 | name: "cryptolvm", 35 | }, 36 | // cryptkey= 37 | { 38 | in: "cryptdevice=/dev/sda2:root cryptkey=rootfs:/var/rootfs.key\n", 39 | name: "root", 40 | key: Keyfile{Path: "/var/rootfs.key"}, 41 | }, 42 | { 43 | in: "cryptdevice=/dev/sda2:root cryptkey=/dev/sdb:512:1024\n", 44 | name: "root", 45 | key: Keyfile{Path: "/dev/sdb", Offset: 512, Size: 1024}, 46 | }, 47 | // errors 48 | { 49 | in: "BOOT_IMAGE=../vmlinuz-linux rw initrd=../initramfs-linux.img\n", 50 | name: "", 51 | key: Keyfile{}, 52 | err: errors.New("no root cryptdevice"), 53 | }, 54 | } 55 | 56 | for _, row := range data { 57 | err := ioutil.WriteFile(kernelCmdline, []byte(row.in), 0644) 58 | if err != nil { 59 | t.Errorf("unexpected error: %#v", err) 60 | } 61 | 62 | name, key, err := parseKernelCmdline() 63 | if name != row.name { 64 | t.Errorf("%#v != %#v", name, row.name) 65 | } 66 | if key != row.key { 67 | t.Errorf("%#v != %#v", name, row.key) 68 | } 69 | if (err == nil) != (row.err == nil) { 70 | t.Errorf("%#v !~ %#v", err, row.err) 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/goLuksSuspend/keyfile.go: -------------------------------------------------------------------------------- 1 | package goLuksSuspend 2 | 3 | import ( 4 | "os" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | type Keyfile struct { 10 | Device string 11 | FSType string 12 | Header string 13 | Path string 14 | Offset uint64 15 | Size uint64 16 | KeySlot uint8 17 | } 18 | 19 | func parseCrypttabEntry(line string) (name string, key Keyfile) { 20 | fields := strings.Fields(line) 21 | 22 | // fields: name, device, keyfile, options 23 | // 24 | // crypttab(5): 25 | // The third field specifies the encryption password. If the field is 26 | // not present or the password is set to "none" or "-", the password 27 | // has to be manually entered during system boot. 28 | if len(fields) < 3 || fields[2] == "-" || fields[2] == "none" { 29 | return "", Keyfile{} 30 | } 31 | 32 | k := Keyfile{Path: fields[2]} 33 | 34 | if len(fields) >= 4 { 35 | opts := strings.Split(fields[3], ",") 36 | for i := range opts { 37 | kv := strings.SplitN(opts[i], "=", 2) 38 | if len(kv) < 2 { 39 | continue 40 | } 41 | 42 | switch kv[0] { 43 | case "keyfile-offset": 44 | n, err := strconv.ParseUint(kv[1], 10, 0) 45 | if err != nil { 46 | continue 47 | } 48 | k.Offset = n 49 | case "keyfile-size": 50 | n, err := strconv.ParseUint(kv[1], 10, 0) 51 | if err != nil { 52 | continue 53 | } 54 | k.Size = n 55 | case "key-slot": 56 | // LUKS currently only supports 8 key slots 57 | n, err := strconv.ParseUint(kv[1], 10, 7) 58 | if err != nil { 59 | continue 60 | } 61 | k.KeySlot = uint8(n | 0x80) 62 | case "header": 63 | k.Header = kv[1] 64 | } 65 | } 66 | } 67 | 68 | return fields[0], k 69 | } 70 | 71 | func (k *Keyfile) Defined() bool { 72 | return len(k.Path) > 0 73 | } 74 | 75 | func (k *Keyfile) needsMount() bool { 76 | return len(k.Device) > 0 77 | } 78 | 79 | func (k *Keyfile) Available() bool { 80 | if !k.Defined() { 81 | return false 82 | } 83 | f := k.Path 84 | if k.needsMount() { 85 | f = k.Device 86 | } 87 | _, err := os.Stat(f) 88 | return !os.IsNotExist(err) 89 | } 90 | 91 | func (k *Keyfile) KeySlotDefined() bool { 92 | return k.KeySlot&0x80 > 0 93 | } 94 | 95 | func (k *Keyfile) GetKeySlot() uint64 { 96 | return uint64(k.KeySlot & 0x7f) 97 | } 98 | -------------------------------------------------------------------------------- /src/goLuksSuspend/keyfile_test.go: -------------------------------------------------------------------------------- 1 | package goLuksSuspend 2 | 3 | import "testing" 4 | 5 | func TestParseKeyfileFromCrypttabEntry(t *testing.T) { 6 | data := []struct { 7 | in string 8 | name string 9 | key Keyfile 10 | }{ 11 | // Malformed input 12 | {in: "foo"}, 13 | {in: "foo bar"}, 14 | {in: " foo\tbar "}, 15 | // Keyfiles with no options 16 | { 17 | in: "crypt1 UUID=f7dd3b0e-b7ae-4f7c-8c31-4895e4c23231 /root/.keys/crypt1.key", 18 | name: "crypt1", 19 | key: Keyfile{Path: "/root/.keys/crypt1.key"}, 20 | }, 21 | { 22 | in: "crypt1 UUID=f7dd3b0e-b7ae-4f7c-8c31-4895e4c23231 /root/.keys/crypt1.key luks,noauto", 23 | name: "crypt1", 24 | key: Keyfile{Path: "/root/.keys/crypt1.key"}, 25 | }, 26 | // Keyfiles with offset and size 27 | { 28 | in: "crypt2 UUID=f7dd3b0e-b7ae-4f7c-8c31-4895e4c23231 /root/.keys/crypt2.key keyfile-size=512,luks,noauto,keyfile-offset=1024", 29 | name: "crypt2", 30 | key: Keyfile{Path: "/root/.keys/crypt2.key", Size: 512, Offset: 1024}, 31 | }, 32 | { 33 | in: "crypt2 UUID=f7dd3b0e-b7ae-4f7c-8c31-4895e4c23231 /root/.keys/crypt2.key keyfile-size=512,luks,keyfile-size=1024,noauto", 34 | name: "crypt2", 35 | key: Keyfile{Path: "/root/.keys/crypt2.key", Size: 1024}, 36 | }, 37 | { 38 | in: "crypt2 UUID=f7dd3b0e-b7ae-4f7c-8c31-4895e4c23231 /root/.keys/crypt2.key keyfile-size=foo,keyfile-size=4096,,keyfile-offset=1024,luks,,,noauto,keyfile-offset=bar", 39 | name: "crypt2", 40 | key: Keyfile{Path: "/root/.keys/crypt2.key", Size: 4096, Offset: 1024}, 41 | }, 42 | // Keyfiles with headers and key-slots 43 | { 44 | in: "crypt3 UUID=f7dd3b0e-b7ae-4f7c-8c31-4895e4c23231 /root/.keys/crypt3.key header=/root/.keys/crypt3.header,key-slot=2", 45 | name: "crypt3", 46 | key: Keyfile{Path: "/root/.keys/crypt3.key", Header: "/root/.keys/crypt3.header", KeySlot: 0x82}, 47 | }, 48 | { 49 | in: "crypt3 UUID=f7dd3b0e-b7ae-4f7c-8c31-4895e4c23231 /root/.keys/crypt3.key key-slot=0", 50 | name: "crypt3", 51 | key: Keyfile{Path: "/root/.keys/crypt3.key", KeySlot: 0x80}, 52 | }, 53 | { 54 | in: "crypt3 UUID=f7dd3b0e-b7ae-4f7c-8c31-4895e4c23231 /root/.keys/crypt3.key key-slot=127", 55 | name: "crypt3", 56 | key: Keyfile{Path: "/root/.keys/crypt3.key", KeySlot: 0xff}, 57 | }, 58 | { 59 | in: "crypt3 UUID=f7dd3b0e-b7ae-4f7c-8c31-4895e4c23231 /root/.keys/crypt3.key key-slot=128", 60 | name: "crypt3", 61 | key: Keyfile{Path: "/root/.keys/crypt3.key", KeySlot: 0}, 62 | }, 63 | { 64 | in: "crypt3 UUID=f7dd3b0e-b7ae-4f7c-8c31-4895e4c23231 /root/.keys/crypt3.key key-slot=-1", 65 | name: "crypt3", 66 | key: Keyfile{Path: "/root/.keys/crypt3.key", KeySlot: 0}, 67 | }, 68 | } 69 | 70 | for _, row := range data { 71 | name, key := parseCrypttabEntry(row.in) 72 | 73 | if name != row.name { 74 | t.Errorf("%#v != %#v", name, row.name) 75 | } 76 | 77 | if key != row.key { 78 | t.Errorf("%#v != %#v", key, row.key) 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/goLuksSuspend/lib.go: -------------------------------------------------------------------------------- 1 | package goLuksSuspend 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "os" 9 | "os/exec" 10 | "strings" 11 | ) 12 | 13 | var DebugMode = false 14 | var PoweroffOnError = false 15 | var IgnoreErrors = false 16 | 17 | func ParseFlags() { 18 | debugFlag := flag.Bool("debug", false, "print debug messages and spawn a shell on errors") 19 | poweroffFlag := flag.Bool("poweroff", false, "power off on errors and failure to unlock root device") 20 | versionFlag := flag.Bool("version", false, "print version and exit") 21 | 22 | flag.Parse() 23 | 24 | if *versionFlag { 25 | fmt.Println(Version) 26 | os.Exit(0) 27 | } 28 | 29 | DebugMode = *debugFlag 30 | PoweroffOnError = *poweroffFlag 31 | } 32 | 33 | func Debug(msg string) { 34 | if DebugMode { 35 | Warn(msg) 36 | } 37 | } 38 | 39 | func Warn(msg string) { 40 | log.Println(msg) 41 | } 42 | 43 | func Assert(err error) { 44 | if err == nil { 45 | return 46 | } 47 | 48 | Warn(err.Error()) 49 | 50 | if IgnoreErrors { 51 | return 52 | } 53 | 54 | if DebugMode { 55 | DebugShell() 56 | } else if PoweroffOnError { 57 | Poweroff() 58 | } else { 59 | os.Exit(1) 60 | } 61 | } 62 | 63 | func DebugShell() { 64 | log.Println("===========================") 65 | log.Println(" DEBUG SHELL ") 66 | log.Println("===========================") 67 | 68 | cmd := exec.Command("/bin/sh") 69 | cmd.Env = []string{"PS1=[\\w \\u\\$] "} 70 | cmd.Stdin = os.Stdin 71 | cmd.Stdout = os.Stdout 72 | cmd.Stderr = os.Stderr 73 | 74 | _ = cmd.Run() 75 | 76 | fmt.Println("EXIT DEBUG SHELL") 77 | } 78 | 79 | func Run(cmd *exec.Cmd) error { 80 | if DebugMode { 81 | if len(cmd.Args) > 0 { 82 | Warn("exec: " + strings.Join(cmd.Args, " ")) 83 | } else { 84 | Warn("exec: " + cmd.Path) 85 | } 86 | } 87 | return cmd.Run() 88 | } 89 | 90 | func Cryptsetup(args ...string) error { 91 | return Run(exec.Command("/usr/bin/cryptsetup", args...)) 92 | } 93 | 94 | func Systemctl(args ...string) error { 95 | return Run(exec.Command("/usr/bin/systemctl", args...)) 96 | } 97 | 98 | const freezeTimeoutPath = "/sys/power/pm_freeze_timeout" 99 | 100 | func SetFreezeTimeout(timeout []byte) (oldtimeout []byte, err error) { 101 | oldtimeout, err = ioutil.ReadFile(freezeTimeoutPath) 102 | if err != nil { 103 | return nil, err 104 | } 105 | return oldtimeout, ioutil.WriteFile(freezeTimeoutPath, timeout, 0644) 106 | } 107 | 108 | func SuspendToRAM() error { 109 | err := ioutil.WriteFile("/sys/power/state", []byte{'m', 'e', 'm'}, 0600) 110 | if err != nil { 111 | return fmt.Errorf("%s\n\nSuspend to RAM failed. Unlock the root volume and investigate `dmesg`.", err.Error()) 112 | } 113 | return nil 114 | } 115 | 116 | func Poweroff() { 117 | for { 118 | _ = ioutil.WriteFile("/proc/sysrq-trigger", []byte{'o'}, 0600) // errcheck: POWERING OFF! 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/goLuksSuspend/version.go: -------------------------------------------------------------------------------- 1 | package goLuksSuspend 2 | 3 | // WARNING: This constant is redefined at build time. 4 | const Version = "v1.4.3" 5 | -------------------------------------------------------------------------------- /vendor/manifest: -------------------------------------------------------------------------------- 1 | { 2 | "version": 0, 3 | "dependencies": [ 4 | { 5 | "importpath": "github.com/guns/golibs/editreader", 6 | "repository": "https://github.com/guns/golibs", 7 | "revision": "4a8a7e3d8cb0df7393fcc2f47fa9e4da6455dad4", 8 | "branch": "master", 9 | "path": "/editreader" 10 | }, 11 | { 12 | "importpath": "github.com/guns/golibs/errutil", 13 | "repository": "https://github.com/guns/golibs", 14 | "revision": "4a8a7e3d8cb0df7393fcc2f47fa9e4da6455dad4", 15 | "branch": "master", 16 | "path": "/errutil" 17 | }, 18 | { 19 | "importpath": "github.com/guns/golibs/process", 20 | "repository": "https://github.com/guns/golibs", 21 | "revision": "4a8a7e3d8cb0df7393fcc2f47fa9e4da6455dad4", 22 | "branch": "master", 23 | "path": "/process" 24 | }, 25 | { 26 | "importpath": "github.com/guns/golibs/sys", 27 | "repository": "https://github.com/guns/golibs", 28 | "revision": "6e9b962f9a5bbd782d53163b7718e091b3cdd780", 29 | "branch": "linux", 30 | "path": "/sys" 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /vendor/src/github.com/guns/golibs/editreader/editreader.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Sung Pae 2 | // Distributed under the MIT license. 3 | // http://www.opensource.org/licenses/mit-license.php 4 | 5 | // Package editreader provides a buffered reader that enables basic editing. 6 | package editreader 7 | 8 | import ( 9 | "io" 10 | "unicode" 11 | ) 12 | 13 | // An Op communicates an edit operation. 14 | type Op byte 15 | 16 | // When multiple Ops are combined, each action will be executed in 17 | // least-significant-order. 18 | // 19 | // Erase | Append | Flush | Close 20 | // 21 | // For example, the value above instructs an editreader to erase the last byte 22 | // in the buffer, append the current byte, flush the buffer, and finally close. 23 | const ( 24 | Erase Op = 1 << iota // Clear and remove one byte at end of buffer 25 | EraseWord // Clear and remove non-whitespace sequence at end of buffer 26 | Kill // Truncate entire buffer 27 | Append // Append to buffer 28 | Flush // Flush the buffer to readers 29 | Close // Close this editreader and send EOF to future readers 30 | ) 31 | 32 | // An EditFn specifies the semantics of an editreader. i is the index of the 33 | // buffer where b would be appended. 34 | type EditFn func(i int, b byte) Op 35 | 36 | // T wraps a Reader and implements an editable buffer by reading one byte at a 37 | // time from the wrapped Reader and executing the Ops returned by an EditFn. 38 | type T struct { 39 | f EditFn 40 | src io.Reader // Source 41 | rbuf []byte // 1-byte buffer for reading from r 42 | buf []byte // Edit buffer 43 | r int // Next edit buffer read index 44 | w int // Next edit buffer write index 45 | err error // Error to return readers after close 46 | available bool // Set on Flush, cleared after full read 47 | overflow bool // Set when an Append would overflow the edit buffer 48 | done bool // Set on read error/EOF or Close Op 49 | secure bool // Clear edit buffer when appropriate 50 | } 51 | 52 | // New returns an editreader with an buffer buflen bytes long. If f is nil, 53 | // BasicLineEdit is used as the edit function. If buflen is non-positive, a 54 | // default buflen of 4096 is used. 55 | // 56 | // Unflushed input that exceeds buflen is flushed automatically. 57 | // 58 | // If secure is true, the buffer is cleared on Kill, Close, and after reads. 59 | // Note that input that is flushed but unread is not cleared on Close. Since 60 | // the data will be cleared on read, callers should make sure to drain the 61 | // editreader if they care about zeroing buffers. Erase and EraseWord always 62 | // zero the bytes they erase. 63 | func New(r io.Reader, buflen int, secure bool, f EditFn) *T { 64 | if buflen < 1 { 65 | buflen = 4096 66 | } 67 | if f == nil { 68 | f = BasicLineEdit 69 | } 70 | return &T{ 71 | f: f, 72 | src: r, 73 | rbuf: make([]byte, 1), 74 | buf: make([]byte, buflen), 75 | secure: secure, 76 | } 77 | } 78 | 79 | // Read reads from the edit buffer if it is available for read. If there is no 80 | // data available, data is read and processed from the source reader until it 81 | // is flushed and available for read. 82 | func (e *T) Read(dst []byte) (n int, err error) { 83 | for { 84 | if e.available { 85 | return e.readAvailable(dst) 86 | } else if e.done { 87 | return 0, e.err 88 | } 89 | e.scan() 90 | } 91 | } 92 | 93 | // WriteTo implements WriterTo, and has been explicitly provided to avoid use 94 | // of a transfer buffer in io.Copy, which is called by exec.Cmd to pipe data 95 | // from a non-file stdin. 96 | func (e *T) WriteTo(w io.Writer) (n int64, err error) { 97 | buf := make([]byte, 4096) 98 | for { 99 | i, rerr := e.Read(buf) 100 | if i > 0 { 101 | j, werr := w.Write(buf[:i]) 102 | n += int64(j) 103 | if werr != nil { 104 | err = werr 105 | break 106 | } 107 | } 108 | if rerr != nil { 109 | if rerr != io.EOF { 110 | err = rerr 111 | } 112 | break 113 | } 114 | } 115 | if e.secure { 116 | clearbytes(buf) 117 | } 118 | return n, err 119 | } 120 | 121 | // readAvailable copies unread data into dst. The available flag is cleared if 122 | // no unread data remains. 123 | // WARNING: This method assumes the buffer is available for read! 124 | func (e *T) readAvailable(dst []byte) (n int, err error) { 125 | n = copy(dst, e.buf[e.r:e.w]) 126 | i := e.r + n 127 | if e.secure { 128 | clearbytes(e.buf[e.r:i]) 129 | } 130 | e.r = i 131 | if e.r >= e.w { 132 | e.r = 0 133 | e.w = 0 134 | e.available = false 135 | } 136 | return n, nil 137 | } 138 | 139 | // scan reads and processes one byte from the source reader. If there was an 140 | // overflow from the last scan, no read occurs and the previously read byte is 141 | // processed instead. 142 | func (e *T) scan() { 143 | if e.overflow { 144 | e.overflow = false 145 | e.process(e.rbuf[0]) 146 | return 147 | } 148 | 149 | n, err := e.src.Read(e.rbuf) 150 | if n > 0 { 151 | e.process(e.rbuf[0]) 152 | } 153 | if err != nil { 154 | e.closeWithError(err) 155 | } 156 | } 157 | 158 | // process executes the Ops specified by the EditFn for byte b. 159 | func (e *T) process(b byte) { 160 | ops := e.f(e.w, b) 161 | 162 | if ops&Erase > 0 { 163 | e.erase() 164 | } 165 | if ops&EraseWord > 0 { 166 | e.eraseWord() 167 | } 168 | if ops&Kill > 0 { 169 | e.kill() 170 | } 171 | if ops&Append > 0 { 172 | e.append(b) 173 | } 174 | if ops&Flush > 0 { 175 | e.available = true 176 | } 177 | if ops&Close > 0 { 178 | e.closeWithError(nil) 179 | } 180 | } 181 | 182 | // erase prunes and zeroes the last byte in the edit buffer. 183 | func (e *T) erase() { 184 | if e.w <= 0 { 185 | return 186 | } 187 | e.w-- 188 | e.buf[e.w] = 0 189 | } 190 | 191 | // eraseWord prunes the last sequence of non-whitespace bytes in the write 192 | // buffer. Multibyte character sequences are unsupported. 193 | func (e *T) eraseWord() { 194 | if e.w <= 0 { 195 | return 196 | } 197 | 198 | // number of boundary transitions 199 | n := 2 200 | 201 | e.w-- 202 | if isWordRune(rune(e.buf[e.w])) { 203 | n-- 204 | } 205 | e.buf[e.w] = 0 206 | 207 | for e.w > 0 { 208 | e.w-- 209 | isword := isWordRune(rune(e.buf[e.w])) 210 | if n == 2 && isword { 211 | n-- 212 | } else if n == 1 && !isword { 213 | e.w++ 214 | break 215 | } 216 | e.buf[e.w] = 0 217 | } 218 | } 219 | 220 | // kill truncates the edit buffer 221 | func (e *T) kill() { 222 | if e.secure { 223 | clearbytes(e.buf[:e.w]) 224 | } 225 | e.w = 0 226 | } 227 | 228 | // append appends a byte to the edit buffer. If this would overflow the 229 | // buffer, the overflow flag is set and the buffer is marked as available. 230 | func (e *T) append(b byte) { 231 | if e.w >= len(e.buf) { 232 | e.overflow = true 233 | e.available = true 234 | return 235 | } 236 | e.buf[e.w] = b 237 | e.w++ 238 | } 239 | 240 | // closeWithError marks e as done, and sets err as the error to send to readers. If err 241 | // is nil, io.EOF is sent. 242 | func (e *T) closeWithError(err error) { 243 | if e.done { 244 | return 245 | } 246 | e.done = true 247 | e.err = err 248 | if err == nil { 249 | e.err = io.EOF 250 | } 251 | if e.secure { 252 | e.rbuf[0] = 0 253 | n := e.w 254 | if e.available { 255 | n = e.r 256 | } 257 | clearbytes(e.buf[:n]) 258 | } 259 | } 260 | 261 | // BasicLineEdit specifies a simple line editor. 262 | func BasicLineEdit(i int, b byte) Op { 263 | switch b { 264 | case '\b', 0x7f: // ^H, ^? 265 | return Erase 266 | case 0x17: // ^W 267 | return EraseWord 268 | case 0x15: // ^U 269 | return Kill 270 | case 0x04: // ^D 271 | if i == 0 { 272 | return Close 273 | } 274 | return Flush 275 | case '\n': 276 | return Append | Flush 277 | default: 278 | return Append 279 | } 280 | } 281 | 282 | // clearbytes zeros a byte slice. 283 | func clearbytes(bs []byte) { 284 | for i := range bs { 285 | bs[i] = 0 286 | } 287 | } 288 | 289 | func isWordRune(r rune) bool { 290 | return r != ' ' && unicode.IsPrint(r) 291 | } 292 | -------------------------------------------------------------------------------- /vendor/src/github.com/guns/golibs/editreader/editreader_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Sung Pae 2 | // Distributed under the MIT license. 3 | // http://www.opensource.org/licenses/mit-license.php 4 | 5 | package editreader 6 | 7 | import ( 8 | "bytes" 9 | "io" 10 | "strings" 11 | "testing" 12 | "unicode" 13 | ) 14 | 15 | func TestEditReader(t *testing.T) { 16 | data := []struct { 17 | input string 18 | rlen int 19 | wlen int 20 | editFn EditFn 21 | expect string 22 | }{ 23 | // Simple inputs 24 | {input: "\n", expect: "\n"}, 25 | {input: "foo bar baz\n", expect: "foo bar baz\n"}, 26 | {input: "lorem ipsum dolor\n\nsit amet\n", expect: "lorem ipsum dolor\n\nsit amet\n"}, 27 | {input: "\x00\x01\n\x02\n", expect: "\x00\x01\n\x02\n"}, 28 | 29 | // Unflushed inputs 30 | {input: "", expect: ""}, 31 | {input: "foo bar baz", expect: ""}, 32 | {input: "foo\nbar baz", expect: "foo\n"}, 33 | 34 | // Partial reads 35 | {input: "1234567890\n1234567890\n", wlen: 3, expect: "1234567890\n1234567890\n"}, 36 | 37 | // Overflow 38 | {input: "1234567890\n", rlen: 3, expect: "1234567890\n"}, 39 | {input: "1234567890", rlen: 3, expect: "123456789"}, 40 | 41 | // Erase 42 | {input: "\b\n", expect: "\n"}, 43 | {input: "12\x7f\b\x7f345\n", expect: "345\n"}, 44 | {input: "1234567\b\x7f\b890\n", expect: "1234890\n"}, 45 | 46 | // EraseWord 47 | {input: "\x17\n", expect: "\n"}, 48 | {input: " \x17\n", expect: "\n"}, 49 | {input: "foo\x17\n", expect: "\n"}, 50 | {input: "foo \t\x17\n", expect: "\n"}, 51 | {input: "foo bar\x17\n", expect: "foo \n"}, 52 | {input: "foo bar \x17\n", expect: "foo \n"}, 53 | {input: "foo bar\x00\x01\x02\x17\n", expect: "foo \n"}, 54 | 55 | // Kill 56 | {input: "\x15\n", expect: "\n"}, 57 | {input: "foo bar baz\x15\n", expect: "\n"}, 58 | {input: "foo\nbar \x15baz\n", expect: "foo\nbaz\n"}, 59 | 60 | // Close 61 | {input: "\x04", expect: ""}, 62 | {input: "foo\x04", expect: "foo"}, 63 | {input: "foo\x04\x04bar\n", expect: "foo"}, 64 | 65 | // Custom EditFn 66 | { 67 | input: "one 2 three 4 five\n", 68 | editFn: func(i int, b byte) Op { 69 | if unicode.IsNumber(rune(b)) { 70 | return 0 71 | } 72 | return BasicLineEdit(i, b) 73 | }, 74 | expect: "one three five\n", 75 | }, 76 | { 77 | // This case tests handling of unread data after close 78 | input: "foo\b\x7f\x17\x15\x04bar\n\x07baz\n\x07", 79 | editFn: func(i int, b byte) Op { 80 | if b == 0x07 { 81 | return Flush | Close 82 | } 83 | return Append 84 | }, 85 | expect: "foo\b\x7f\x17\x15\x04bar\n", 86 | }, 87 | } 88 | 89 | for _, row := range data { 90 | for _, writeTo := range []bool{false, true} { 91 | if row.wlen == 0 { 92 | row.wlen = 80 93 | } 94 | 95 | r := New(strings.NewReader(row.input), row.rlen, true, row.editFn) 96 | out := make([]byte, 0, 256) 97 | buf := make([]byte, row.wlen) 98 | var n int 99 | var err error 100 | 101 | out, buf, err = readall(r, out, buf, writeTo) 102 | if err != io.EOF { 103 | t.Errorf("unexpected non-EOF error: %#v", err) 104 | } 105 | 106 | // Read once more; should get (0, EOF) 107 | n, err = r.Read(buf) 108 | if n != 0 { 109 | t.Errorf("%#v != %#v", n, 0) 110 | } 111 | if err != io.EOF { 112 | t.Errorf("%#v != %#v", err, io.EOF) 113 | } 114 | 115 | if len(r.rbuf) != 1 { 116 | t.Errorf("%#v != %#v", len(r.rbuf), 1) 117 | } 118 | 119 | if r.rbuf[0] != 0 { 120 | t.Errorf("should be clear: %#v", r.rbuf[0]) 121 | } 122 | 123 | for i := range r.buf { 124 | if r.buf[i] != 0 { 125 | t.Errorf("should be clear: %#v", string(r.buf)) 126 | break 127 | } 128 | } 129 | 130 | if string(out) != row.expect { 131 | t.Errorf("%#v != %#v", string(out), row.expect) 132 | } 133 | } 134 | } 135 | } 136 | 137 | func readall(r *T, out, buf []byte, writeTo bool) ([]byte, []byte, error) { 138 | var n int 139 | var err error 140 | 141 | if writeTo { 142 | bbuf := bytes.Buffer{} 143 | _, err = r.WriteTo(&bbuf) 144 | out = append(out, bbuf.Bytes()...) 145 | if err == nil { 146 | err = io.EOF 147 | } 148 | } else { 149 | for { 150 | n, err = r.Read(buf) 151 | if n > 0 { 152 | out = append(out, buf[:n]...) 153 | } 154 | if err != nil { 155 | break 156 | } 157 | } 158 | } 159 | 160 | return out, buf, err 161 | } 162 | -------------------------------------------------------------------------------- /vendor/src/github.com/guns/golibs/errutil/errutil.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Sung Pae 2 | // Distributed under the MIT license. 3 | // http://www.opensource.org/licenses/mit-license.php 4 | 5 | // Package errutil provides a simple functions for working with errors. 6 | package errutil 7 | 8 | import ( 9 | "errors" 10 | "strings" 11 | ) 12 | 13 | // Join joins error messages with a given separator. Nil arguments are 14 | // ignored, and if all arguments are nil, Join returns nil. 15 | func Join(sep string, errs ...error) error { 16 | var errorStrings []string 17 | 18 | for i := range errs { 19 | if errs[i] != nil { 20 | errorStrings = append(errorStrings, errs[i].Error()) 21 | } 22 | } 23 | 24 | if len(errorStrings) == 0 { 25 | return nil 26 | } 27 | 28 | return errors.New(strings.Join(errorStrings, sep)) 29 | } 30 | 31 | // First returns the first non-nil error in errs. 32 | func First(errs ...error) error { 33 | for i := range errs { 34 | if errs[i] != nil { 35 | return errs[i] 36 | } 37 | } 38 | return nil 39 | } 40 | -------------------------------------------------------------------------------- /vendor/src/github.com/guns/golibs/errutil/errutil_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Sung Pae 2 | // Distributed under the MIT license. 3 | // http://www.opensource.org/licenses/mit-license.php 4 | 5 | package errutil 6 | 7 | import ( 8 | "errors" 9 | "reflect" 10 | "testing" 11 | ) 12 | 13 | func TestJoin(t *testing.T) { 14 | data := []struct { 15 | in []error 16 | out error 17 | }{ 18 | {[]error{}, nil}, 19 | {[]error{nil}, nil}, 20 | {[]error{nil, nil}, nil}, 21 | {[]error{errors.New("a"), nil}, errors.New("a")}, 22 | {[]error{nil, errors.New("b")}, errors.New("b")}, 23 | {[]error{errors.New("a"), nil, errors.New("c")}, errors.New("a; c")}, 24 | {[]error{errors.New("a"), errors.New("b"), errors.New("c")}, errors.New("a; b; c")}, 25 | } 26 | 27 | for _, row := range data { 28 | err := Join("; ", row.in...) 29 | if !reflect.DeepEqual(err, row.out) { 30 | t.Errorf("%#v != %#v", err, row.out) 31 | } 32 | } 33 | } 34 | 35 | func TestFirst(t *testing.T) { 36 | err1 := errors.New("1") 37 | err2 := errors.New("2") 38 | err3 := errors.New("3") 39 | 40 | data := []struct { 41 | in []error 42 | out error 43 | }{ 44 | {[]error{}, nil}, 45 | {[]error{nil}, nil}, 46 | {[]error{nil, nil}, nil}, 47 | {[]error{err3, err2, err1}, err3}, 48 | {[]error{nil, err2, err1}, err2}, 49 | {[]error{nil, nil, err1}, err1}, 50 | } 51 | 52 | for _, row := range data { 53 | err := First(row.in...) 54 | if err != row.out { 55 | t.Errorf("%#v != %#v", err, row.out) 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /vendor/src/github.com/guns/golibs/process/terminate.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2017 Sung Pae 2 | // Distributed under the MIT license. 3 | // http://www.opensource.org/licenses/mit-license.php 4 | 5 | // +build !windows 6 | 7 | // Package process provides tools for working with OS processes. 8 | package process 9 | 10 | import ( 11 | "os" 12 | "syscall" 13 | "time" 14 | ) 15 | 16 | // IsAlive implements `kill -0`. Note that calling IsAlive() on a zombie process 17 | // will return true. 18 | func IsAlive(p *os.Process) bool { 19 | if p == nil { 20 | return false 21 | } 22 | return p.Signal(syscall.Signal(0)) == nil 23 | } 24 | 25 | // Terminate sends SIGTERM to a command process, then sends SIGKILL after 26 | // timeout if it is still alive. The process is not reaped, and zombies are 27 | // considered to be alive. 28 | func Terminate(p *os.Process, timeout time.Duration) { 29 | if !IsAlive(p) { 30 | return 31 | } 32 | 33 | // Notify the process politely 34 | if err := p.Signal(syscall.SIGTERM); err != nil { 35 | return 36 | } 37 | 38 | time.Sleep(timeout) 39 | 40 | if !IsAlive(p) { 41 | return 42 | } 43 | 44 | _ = p.Kill() // errcheck: If SIGKILL fails, what's really to be done? 45 | } 46 | -------------------------------------------------------------------------------- /vendor/src/github.com/guns/golibs/process/terminate_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2017 Sung Pae 2 | // Distributed under the MIT license. 3 | // http://www.opensource.org/licenses/mit-license.php 4 | 5 | package process 6 | 7 | import ( 8 | "os/exec" 9 | "reflect" 10 | "testing" 11 | "time" 12 | ) 13 | 14 | func TestIsAlive(t *testing.T) { 15 | cmd := exec.Command("sleep", "1") 16 | err := cmd.Start() 17 | if err != nil { 18 | t.Errorf("unexpected error: %v", err) 19 | } 20 | if !IsAlive(cmd.Process) { 21 | t.Errorf("expected: IsAlive(cmd.Process)") 22 | } 23 | 24 | err = cmd.Process.Kill() 25 | if err != nil { 26 | t.Errorf("unexpected error: %v", err) 27 | } 28 | 29 | time.Sleep(5 * time.Millisecond) 30 | 31 | if !IsAlive(cmd.Process) { 32 | t.Errorf("expected: IsAlive(cmd.Process)") // zombies are alive 33 | } 34 | 35 | err = cmd.Wait() 36 | if err == nil { 37 | t.Errorf("expected err to be an error, but got nil") 38 | } 39 | 40 | if IsAlive(cmd.Process) { 41 | t.Errorf("expected: !IsAlive(cmd.Process)") 42 | } 43 | 44 | if IsAlive(nil) { 45 | t.Errorf("expected: !IsAlive(nil)") 46 | } 47 | } 48 | 49 | func TestTerminate(t *testing.T) { 50 | data := []struct { 51 | cmd []string 52 | lower, upper time.Duration 53 | err error 54 | }{ 55 | { 56 | []string{"sleep", "1"}, 57 | 0, 58 | 15 * time.Millisecond, 59 | &exec.ExitError{}, 60 | }, 61 | { 62 | []string{"sh", "-c", "trap '' TERM; sleep 1"}, 63 | 100 * time.Millisecond, 64 | 900 * time.Millisecond, 65 | &exec.ExitError{}, 66 | }, 67 | { 68 | []string{"true"}, 69 | 0, 70 | 10 * time.Millisecond, 71 | nil, 72 | }, 73 | } 74 | 75 | for _, row := range data { 76 | cmd := exec.Command(row.cmd[0], row.cmd[1:]...) 77 | start := time.Now() 78 | err := cmd.Start() 79 | if err != nil { 80 | t.Errorf("unexpected error: %v", err) 81 | } 82 | reaper := make(chan error, 1) 83 | go func() { 84 | reaper <- cmd.Wait() 85 | close(reaper) 86 | }() 87 | time.Sleep(5 * time.Millisecond) // Process setup time 88 | go Terminate(cmd.Process, 100*time.Millisecond) 89 | err = <-reaper 90 | elapsed := time.Since(start) 91 | if !(row.lower <= elapsed && elapsed <= row.upper) { 92 | t.Errorf("expected: %v ≤ elapsed ≤ %v, actual: %v", row.lower, row.upper, elapsed) 93 | } 94 | if reflect.TypeOf(err) != reflect.TypeOf(row.err) { 95 | t.Errorf("expected type %v, but got type %v", reflect.TypeOf(row.err), reflect.TypeOf(err)) 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /vendor/src/github.com/guns/golibs/sys/sys.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Sung Pae 2 | // Distributed under the MIT license. 3 | // http://www.opensource.org/licenses/mit-license.php 4 | 5 | // +build !windows 6 | 7 | // Package sys provides convenience functions around syscalls. 8 | package sys 9 | 10 | import "syscall" 11 | 12 | // Ioctl is a simple wrapper around syscall.Syscall(syscall.SYS_IOCTL, …). 13 | // Returns nil if the syscall.Errno equals 0. 14 | func Ioctl(a1, a2, a3 uintptr) (r1, r2 uintptr, err error) { 15 | r1, r2, errno := syscall.Syscall(syscall.SYS_IOCTL, a1, a2, a3) 16 | if errno != 0 { 17 | err = errno 18 | } 19 | return 20 | } 21 | -------------------------------------------------------------------------------- /vendor/src/github.com/guns/golibs/sys/tty.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Sung Pae 2 | // Distributed under the MIT license. 3 | // http://www.opensource.org/licenses/mit-license.php 4 | 5 | // +build linux 6 | 7 | package sys 8 | 9 | import ( 10 | "syscall" 11 | "unsafe" 12 | ) 13 | 14 | // SetTTYIoctl identifies the three tcsetattr ioctls described in ioctl_tty(2) 15 | type SetTTYIoctl uintptr 16 | 17 | // These are typed for compile time safety. 18 | // /usr/include/asm-generic/ioctls.h: 19 | const ( 20 | TCSETS SetTTYIoctl = 0x5402 21 | TCSETSW = 0x5403 22 | TCSETSF = 0x5404 23 | ) 24 | 25 | // GetTTYState writes the TTY state of fd to termios. 26 | func GetTTYState(fd uintptr, termios *syscall.Termios) error { 27 | _, _, err := Ioctl(fd, syscall.TCGETS, uintptr(unsafe.Pointer(termios))) 28 | return err 29 | } 30 | 31 | // SetTTYState alters the TTY state of fd to match termios. 32 | func SetTTYState(fd uintptr, action SetTTYIoctl, termios *syscall.Termios) error { 33 | // tcsetattr(3): 34 | // Note that tcsetattr() returns success if any of the requested changes 35 | // could be successfully carried out. Therefore, when making multiple changes 36 | // it may be necessary to follow this call with a further call to tcgetattr() 37 | // to check that all changes have been performed successfully. 38 | state := syscall.Termios{} 39 | for { 40 | _, _, err := Ioctl(fd, uintptr(action), uintptr(unsafe.Pointer(termios))) 41 | if err != nil { 42 | return err 43 | } 44 | 45 | if err := GetTTYState(fd, &state); err != nil { 46 | return err 47 | } 48 | 49 | if state == *termios { 50 | return nil 51 | } 52 | } 53 | } 54 | 55 | // AlterTTY provides a simple way to change a TTY and restore it later. 56 | // 57 | // Function f receives a copy of the current TTY state of fd and modifies it. 58 | // The TTY indicated by fd is then changed to match this modified state. 59 | // 60 | // A function is returned that will return the TTY to its original state if it 61 | // was altered. If the TTY was not altered, restoreTTY will be nil. 62 | func AlterTTY(fd uintptr, action SetTTYIoctl, f func(*syscall.Termios)) (restoreTTY func() error, err error) { 63 | oldstate := syscall.Termios{} 64 | 65 | if err := GetTTYState(fd, &oldstate); err != nil { 66 | return nil, err 67 | } 68 | 69 | restoreTTY = func() error { return SetTTYState(fd, action, &oldstate) } 70 | 71 | newstate := oldstate 72 | f(&newstate) 73 | 74 | return restoreTTY, SetTTYState(fd, action, &newstate) 75 | } 76 | --------------------------------------------------------------------------------