├── .github ├── CODEOWNERS ├── dependabot.yml └── workflows │ ├── libvirtd.conf │ └── main.yml ├── .gitignore ├── AUTHORS ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── connect_uri.go ├── const.gen.go ├── doc.go ├── go.mod ├── go.sum ├── internal ├── constants │ ├── qemu_protocol.gen.go │ └── remote_protocol.gen.go ├── event │ ├── event.go │ ├── stream.go │ └── stream_test.go ├── go-xdr │ ├── LICENSE │ ├── README.md │ ├── goclean.sh │ └── xdr2 │ │ ├── bench_test.go │ │ ├── decode.go │ │ ├── decode_test.go │ │ ├── doc.go │ │ ├── encode.go │ │ ├── encode_test.go │ │ ├── error.go │ │ ├── error_test.go │ │ ├── example_test.go │ │ ├── fixedIO_test.go │ │ └── internal_test.go └── lvgen │ ├── constants.tmpl │ ├── gen │ └── main.go │ ├── generate.go │ ├── lv-gen.go │ ├── lvlexer.go │ ├── procedures.tmpl │ ├── sunrpc.y │ ├── y.go │ └── y.output ├── libvirt.go ├── libvirt.yml ├── libvirt_integration_test.go ├── libvirt_test.go ├── libvirttest └── libvirt.go ├── qemu_protocol.gen.go ├── remote_protocol.gen.go ├── rpc.go ├── rpc_test.go ├── scripts ├── gen-consts.sh └── licensecheck.sh ├── socket ├── dialers │ ├── already_connected.go │ ├── gossh.go │ ├── local.go │ ├── remote.go │ └── tls.go ├── socket.go ├── socket_test.go └── units.go └── testdata ├── test-domain.xml ├── test-pool.xml └── test-secret.xml /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @digitalocean/go-libvirt 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gomod" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | - package-ecosystem: "github-actions" 8 | directory: "/" 9 | schedule: 10 | interval: "weekly" 11 | -------------------------------------------------------------------------------- /.github/workflows/libvirtd.conf: -------------------------------------------------------------------------------- 1 | # libvirtd configuration for ci 2 | listen_tls = 0 3 | listen_tcp = 1 4 | tcp_port = "16509" 5 | listen_addr = "127.0.0.1" 6 | auth_unix_rw = "none" 7 | auth_tcp = "none" 8 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | # allow manual execution from the web interface 10 | workflow_dispatch: 11 | 12 | jobs: 13 | test: 14 | strategy: 15 | matrix: 16 | os: ['ubuntu-22.04', 'ubuntu-24.04'] 17 | toolchain: ['oldstable', 'stable'] 18 | 19 | runs-on: ${{ matrix.os }} 20 | steps: 21 | - uses: actions/checkout@v4 # respository path: $GITHUB_WORKSPACE 22 | - uses: actions/setup-go@v5 23 | with: 24 | go-version: ${{ matrix.toolchain }} 25 | 26 | - name: Install golint 27 | run: go install golang.org/x/lint/golint@latest 28 | - name: Install goyacc 29 | run: go install golang.org/x/tools/cmd/goyacc@latest 30 | 31 | - name: Install libvirt 32 | run: | 33 | sudo apt-get update 34 | sudo apt-get -qqy install libvirt-daemon-system 35 | 36 | - name: Stop libvirtd 37 | run: sudo systemctl stop libvirtd 38 | - name: Start libvirtd with custom config 39 | run: sudo libvirtd -d -l -f $GITHUB_WORKSPACE/.github/workflows/libvirtd.conf 40 | 41 | - name: Setup test artifacts 42 | env: 43 | TESTDATA: ${{ github.workspace }}/testdata 44 | run: | 45 | go get -d ./... 46 | sudo qemu-img create -f raw -o size=10M /var/lib/libvirt/images/test.raw 47 | sudo virsh define $TESTDATA/test-domain.xml 48 | sudo virsh start test 49 | sudo virsh pool-create $TESTDATA/test-pool.xml 50 | sudo virsh secret-define $TESTDATA/test-secret.xml 51 | 52 | - name: Lint and Vet 53 | run: | 54 | golint -set_exit_status ./... 55 | go vet ./... 56 | 57 | - name: Unit test 58 | run: go test -race ./... 59 | 60 | - name: integration test 61 | run: sudo go test -race -tags=integration ./... 62 | 63 | - name: Build 64 | run: go build ./... 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | bin/ 3 | libvirt/ 4 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Maintainer 2 | ---------- 3 | DigitalOcean, Inc 4 | 5 | Original Authors 6 | ---------------- 7 | Ben LeMasurier 8 | Matt Layher 9 | 10 | Contributors 11 | ------------ 12 | Justin Kim 13 | Ricky Medina 14 | Charlie Drage 15 | Michael Koppmann 16 | Simarpreet Singh 17 | Alexander Polyakov 18 | Amanda Andrade 19 | Geoff Hickey 20 | Yuriy Taraday 21 | Sylvain Baubeau 22 | David Schneider 23 | Alec Hothan 24 | Akos Varga 25 | Peter Kurfer 26 | Sam Roberts 27 | Moritz Wanzenböck 28 | Jenni Griesmann 29 | Zane Bitter 30 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | The `go-libvirt` project makes use of the [GitHub Flow](https://guides.github.com/introduction/flow/) 5 | for contributions. 6 | 7 | If you'd like to contribute to the project, please 8 | [open an issue](https://github.com/digitalocean/go-libvirt/issues/new) or find an 9 | [existing issue](https://github.com/digitalocean/go-libvirt/issues) that you'd like 10 | to take on. This ensures that efforts are not duplicated, and that a new feature 11 | aligns with the focus of the rest of the repository. 12 | 13 | Once your suggestion has been submitted and discussed, please be sure that your 14 | code meets the following criteria: 15 | - code is completely `gofmt`'d 16 | - new features or codepaths have appropriate test coverage 17 | - `go test ./...` passes 18 | - `go vet ./...` passes 19 | - `golint ./...` returns no warnings, including documentation comment warnings 20 | 21 | In addition, if this is your first time contributing to the `go-libvirt` project, 22 | add your name and email address to the 23 | [AUTHORS](https://github.com/digitalocean/go-libvirt/blob/master/AUTHORS) file 24 | under the "Contributors" section using the format: 25 | `First Last `. 26 | 27 | Finally, submit a pull request for review! 28 | 29 | Feel free to join us in [`#go-libvirt` on libera chat](https://web.libera.chat/) 30 | if you'd like to discuss the project. 31 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | ============== 3 | 4 | _Version 2.0, January 2004_ 5 | _<>_ 6 | 7 | ### Terms and Conditions for use, reproduction, and distribution 8 | 9 | #### 1. Definitions 10 | 11 | “License” shall mean the terms and conditions for use, reproduction, and 12 | distribution as defined by Sections 1 through 9 of this document. 13 | 14 | “Licensor” shall mean the copyright owner or entity authorized by the copyright 15 | owner that is granting the License. 16 | 17 | “Legal Entity” shall mean the union of the acting entity and all other entities 18 | that control, are controlled by, or are under common control with that entity. 19 | For the purposes of this definition, “control” means **(i)** the power, direct or 20 | indirect, to cause the direction or management of such entity, whether by 21 | contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the 22 | outstanding shares, or **(iii)** beneficial ownership of such entity. 23 | 24 | “You” (or “Your”) shall mean an individual or Legal Entity exercising 25 | permissions granted by this License. 26 | 27 | “Source” form shall mean the preferred form for making modifications, including 28 | but not limited to software source code, documentation source, and configuration 29 | files. 30 | 31 | “Object” form shall mean any form resulting from mechanical transformation or 32 | translation of a Source form, including but not limited to compiled object code, 33 | generated documentation, and conversions to other media types. 34 | 35 | “Work” shall mean the work of authorship, whether in Source or Object form, made 36 | available under the License, as indicated by a copyright notice that is included 37 | in or attached to the work (an example is provided in the Appendix below). 38 | 39 | “Derivative Works” shall mean any work, whether in Source or Object form, that 40 | is based on (or derived from) the Work and for which the editorial revisions, 41 | annotations, elaborations, or other modifications represent, as a whole, an 42 | original work of authorship. For the purposes of this License, Derivative Works 43 | shall not include works that remain separable from, or merely link (or bind by 44 | name) to the interfaces of, the Work and Derivative Works thereof. 45 | 46 | “Contribution” shall mean any work of authorship, including the original version 47 | of the Work and any modifications or additions to that Work or Derivative Works 48 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 49 | by the copyright owner or by an individual or Legal Entity authorized to submit 50 | on behalf of the copyright owner. For the purposes of this definition, 51 | “submitted” means any form of electronic, verbal, or written communication sent 52 | to the Licensor or its representatives, including but not limited to 53 | communication on electronic mailing lists, source code control systems, and 54 | issue tracking systems that are managed by, or on behalf of, the Licensor for 55 | the purpose of discussing and improving the Work, but excluding communication 56 | that is conspicuously marked or otherwise designated in writing by the copyright 57 | owner as “Not a Contribution.” 58 | 59 | “Contributor” shall mean Licensor and any individual or Legal Entity on behalf 60 | of whom a Contribution has been received by Licensor and subsequently 61 | incorporated within the Work. 62 | 63 | #### 2. Grant of Copyright License 64 | 65 | Subject to the terms and conditions of this License, each Contributor hereby 66 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 67 | irrevocable copyright license to reproduce, prepare Derivative Works of, 68 | publicly display, publicly perform, sublicense, and distribute the Work and such 69 | Derivative Works in Source or Object form. 70 | 71 | #### 3. Grant of Patent License 72 | 73 | Subject to the terms and conditions of this License, each Contributor hereby 74 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 75 | irrevocable (except as stated in this section) patent license to make, have 76 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 77 | such license applies only to those patent claims licensable by such Contributor 78 | that are necessarily infringed by their Contribution(s) alone or by combination 79 | of their Contribution(s) with the Work to which such Contribution(s) was 80 | submitted. If You institute patent litigation against any entity (including a 81 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 82 | Contribution incorporated within the Work constitutes direct or contributory 83 | patent infringement, then any patent licenses granted to You under this License 84 | for that Work shall terminate as of the date such litigation is filed. 85 | 86 | #### 4. Redistribution 87 | 88 | You may reproduce and distribute copies of the Work or Derivative Works thereof 89 | in any medium, with or without modifications, and in Source or Object form, 90 | provided that You meet the following conditions: 91 | 92 | * **(a)** You must give any other recipients of the Work or Derivative Works a copy of 93 | this License; and 94 | * **(b)** You must cause any modified files to carry prominent notices stating that You 95 | changed the files; and 96 | * **(c)** You must retain, in the Source form of any Derivative Works that You distribute, 97 | all copyright, patent, trademark, and attribution notices from the Source form 98 | of the Work, excluding those notices that do not pertain to any part of the 99 | Derivative Works; and 100 | * **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any 101 | Derivative Works that You distribute must include a readable copy of the 102 | attribution notices contained within such NOTICE file, excluding those notices 103 | that do not pertain to any part of the Derivative Works, in at least one of the 104 | following places: within a NOTICE text file distributed as part of the 105 | Derivative Works; within the Source form or documentation, if provided along 106 | with the Derivative Works; or, within a display generated by the Derivative 107 | Works, if and wherever such third-party notices normally appear. The contents of 108 | the NOTICE file are for informational purposes only and do not modify the 109 | License. You may add Your own attribution notices within Derivative Works that 110 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 111 | provided that such additional attribution notices cannot be construed as 112 | modifying the License. 113 | 114 | You may add Your own copyright statement to Your modifications and may provide 115 | additional or different license terms and conditions for use, reproduction, or 116 | distribution of Your modifications, or for any such Derivative Works as a whole, 117 | provided Your use, reproduction, and distribution of the Work otherwise complies 118 | with the conditions stated in this License. 119 | 120 | #### 5. Submission of Contributions 121 | 122 | Unless You explicitly state otherwise, any Contribution intentionally submitted 123 | for inclusion in the Work by You to the Licensor shall be under the terms and 124 | conditions of this License, without any additional terms or conditions. 125 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 126 | any separate license agreement you may have executed with Licensor regarding 127 | such Contributions. 128 | 129 | #### 6. Trademarks 130 | 131 | This License does not grant permission to use the trade names, trademarks, 132 | service marks, or product names of the Licensor, except as required for 133 | reasonable and customary use in describing the origin of the Work and 134 | reproducing the content of the NOTICE file. 135 | 136 | #### 7. Disclaimer of Warranty 137 | 138 | Unless required by applicable law or agreed to in writing, Licensor provides the 139 | Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, 140 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 141 | including, without limitation, any warranties or conditions of TITLE, 142 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 143 | solely responsible for determining the appropriateness of using or 144 | redistributing the Work and assume any risks associated with Your exercise of 145 | permissions under this License. 146 | 147 | #### 8. Limitation of Liability 148 | 149 | In no event and under no legal theory, whether in tort (including negligence), 150 | contract, or otherwise, unless required by applicable law (such as deliberate 151 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 152 | liable to You for damages, including any direct, indirect, special, incidental, 153 | or consequential damages of any character arising as a result of this License or 154 | out of the use or inability to use the Work (including but not limited to 155 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 156 | any and all other commercial damages or losses), even if such Contributor has 157 | been advised of the possibility of such damages. 158 | 159 | #### 9. Accepting Warranty or Additional Liability 160 | 161 | While redistributing the Work or Derivative Works thereof, You may choose to 162 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 163 | other liability obligations and/or rights consistent with this License. However, 164 | in accepting such obligations, You may act only on Your own behalf and on Your 165 | sole responsibility, not on behalf of any other Contributor, and only if You 166 | agree to indemnify, defend, and hold each Contributor harmless for any liability 167 | incurred by, or claims asserted against, such Contributor by reason of your 168 | accepting any such warranty or additional liability. 169 | 170 | _END OF TERMS AND CONDITIONS_ 171 | 172 | ### APPENDIX: How to apply the Apache License to your work 173 | 174 | To apply the Apache License to your work, attach the following boilerplate 175 | notice, with the fields enclosed by brackets `[]` replaced with your own 176 | identifying information. (Don't include the brackets!) The text should be 177 | enclosed in the appropriate comment syntax for the file format. We also 178 | recommend that a file or class name and description of purpose be included on 179 | the same “printed page” as the copyright notice for easier identification within 180 | third-party archives. 181 | 182 | Copyright [yyyy] [name of copyright owner] 183 | 184 | Licensed under the Apache License, Version 2.0 (the "License"); 185 | you may not use this file except in compliance with the License. 186 | You may obtain a copy of the License at 187 | 188 | http://www.apache.org/licenses/LICENSE-2.0 189 | 190 | Unless required by applicable law or agreed to in writing, software 191 | distributed under the License is distributed on an "AS IS" BASIS, 192 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 193 | See the License for the specific language governing permissions and 194 | limitations under the License. 195 | 196 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | libvirt 2 | [![GoDoc](http://godoc.org/github.com/digitalocean/go-libvirt?status.svg)](http://godoc.org/github.com/digitalocean/go-libvirt) 3 | [![Build Status](https://github.com/digitalocean/go-libvirt/actions/workflows/main.yml/badge.svg)](https://github.com/digitalocean/go-libvirt/actions/) 4 | [![Report Card](https://goreportcard.com/badge/github.com/digitalocean/go-libvirt)](https://goreportcard.com/report/github.com/digitalocean/go-libvirt) 5 | ==== 6 | 7 | Package `go-libvirt` provides a pure Go interface for interacting with libvirt. 8 | 9 | Rather than using libvirt's C bindings, this package makes use of 10 | libvirt's RPC interface, as documented [here](https://libvirt.org/kbase/internals/rpc.html). 11 | Connections to the libvirt server may be local, or remote. RPC packets are encoded 12 | using the XDR standard as defined by [RFC 4506](https://tools.ietf.org/html/rfc4506.html). 13 | 14 | libvirt's RPC interface is quite extensive, and changes from one version to the 15 | next, so this project uses a pair of code generators to build the go bindings. 16 | The code generators should be run whenever you want to build go-libvirt for a 17 | new version of libvirt. See the next section for directions on re-generating 18 | go-libvirt. 19 | 20 | [Pull requests are welcome](https://github.com/digitalocean/go-libvirt/blob/master/CONTRIBUTING.md)! 21 | 22 | Feel free to join us in [`#go-libvirt` on libera chat](https://web.libera.chat/) 23 | if you'd like to discuss the project. 24 | 25 | Running the Code Generators 26 | --------------------------- 27 | 28 | The code generator doesn't run automatically when you build go-libvirt. It's 29 | meant to be run manually any time you change the version of libvirt you're 30 | using. When you download go-libvirt it will come with generated files 31 | corresponding to a particular version of libvirt. You can use the library as-is, 32 | but the generated code may be missing libvirt functions, if you're using a newer 33 | version of libvirt, or it may have extra functions that will return 34 | 'unimplemented' errors if you try to call them. If this is a problem, you should 35 | re-run the code generator. To do this, follow these steps: 36 | 37 | - First, download a copy of the libvirt sources corresponding to the version you 38 | want to use. 39 | - Change directories into where you've unpacked your distribution of libvirt. 40 | - The second step depends on the version of libvirt you'd like to build against. 41 | It's not necessary to actually build libvirt, but it is necessary to run libvirt's 42 | "configure" step because it generates required files. 43 | - For libvirt < v6.7.0: 44 | - `$ mkdir build; cd build` 45 | - `$ ../autogen.sh` 46 | - For libvirt >= v6.7.0: 47 | - `$ meson setup build` 48 | - Finally, set the environment variable `LIBVIRT_SOURCE` to the directory you 49 | put libvirt into, and run `go generate ./...` from the go-libvirt directory. 50 | This runs both of the go-libvirt's code generators. 51 | 52 | How to Use This Library 53 | ----------------------- 54 | 55 | Once you've vendored go-libvirt into your project, you'll probably want to call 56 | some libvirt functions. There's some example code below showing how to connect 57 | to libvirt and make one such call, but once you get past the introduction you'll 58 | next want to call some other libvirt functions. How do you find them? 59 | 60 | Start with the [libvirt API reference](https://libvirt.org/html/index.html). 61 | Let's say you want to gracefully shutdown a VM, and after reading through the 62 | libvirt docs you determine that virDomainShutdown() is the function you want to 63 | call to do that. Where's that function in go-libvirt? We transform the names 64 | slightly when building the go bindings. There's no need for a global prefix like 65 | "vir" in Go, since all our functions are inside the package namespace, so we 66 | drop it. That means the Go function for `virDomainShutdown()` is just `DomainShutdown()`, 67 | and sure enough, you can find the Go function `DomainShutdown()` in libvirt.gen.go, 68 | with parameters and return values equivalent to those documented in the API 69 | reference. 70 | 71 | Suppose you then decide you need more control over your shutdown, so you switch 72 | over to `virDomainShutdownFlags()`. As its name suggests, this function takes a 73 | flag parameter which has possible values specified in an enum called 74 | `virDomainShutdownFlagValues`. Flag types like this are a little tricky for the 75 | code generator, because the C functions just take an integer type - only the 76 | libvirt documentation actually ties the flags to the enum types. In most cases 77 | though we're able to generate a wrapper function with a distinct flag type, 78 | making it easier for Go tooling to suggest possible flag values while you're 79 | working. Checking the documentation for this function: 80 | 81 | `godoc github.com/digitalocean/go-libvirt DomainShutdownFlags` 82 | 83 | returns this: 84 | 85 | `func (l *Libvirt) DomainShutdownFlags(Dom Domain, Flags DomainShutdownFlagValues) (err error)` 86 | 87 | If you want to see the possible flag values, `godoc` can help again: 88 | 89 | ``` 90 | $ godoc github.com/digitalocean/go-libvirt DomainShutdownFlagValues 91 | 92 | type DomainShutdownFlagValues int32 93 | DomainShutdownFlagValues as declared in libvirt/libvirt-domain.h:1121 94 | 95 | const ( 96 | DomainShutdownDefault DomainShutdownFlagValues = iota 97 | DomainShutdownAcpiPowerBtn DomainShutdownFlagValues = 1 98 | DomainShutdownGuestAgent DomainShutdownFlagValues = 2 99 | DomainShutdownInitctl DomainShutdownFlagValues = 4 100 | DomainShutdownSignal DomainShutdownFlagValues = 8 101 | DomainShutdownParavirt DomainShutdownFlagValues = 16 102 | ) 103 | DomainShutdownFlagValues enumeration from libvirt/libvirt-domain.h:1121 104 | ``` 105 | 106 | One other suggestion: most of the code in go-libvirt is now generated, but a few 107 | hand-written routines still exist in libvirt.go, and wrap calls to the generated 108 | code with slightly different parameters or return values. We suggest avoiding 109 | these hand-written routines and calling the generated routines in libvirt.gen.go 110 | instead. Over time these handwritten routines will be removed from go-libvirt. 111 | 112 | Warning 113 | ------- 114 | 115 | While these package are reasonably well-tested and have seen some use inside of 116 | DigitalOcean, there may be subtle bugs which could cause the packages to act 117 | in unexpected ways. Use at your own risk! 118 | 119 | In addition, the API is not considered stable at this time. If you would like 120 | to include package `libvirt` in a project, we highly recommend vendoring it into 121 | your project. 122 | 123 | Example 124 | ------- 125 | 126 | ```go 127 | package main 128 | 129 | import ( 130 | "fmt" 131 | "log" 132 | "net/url" 133 | 134 | "github.com/digitalocean/go-libvirt" 135 | ) 136 | 137 | func main() { 138 | uri, _ := url.Parse(string(libvirt.QEMUSystem)) 139 | l, err := libvirt.ConnectToURI(uri) 140 | if err != nil { 141 | log.Fatalf("failed to connect: %v", err) 142 | } 143 | 144 | v, err := l.ConnectGetLibVersion() 145 | if err != nil { 146 | log.Fatalf("failed to retrieve libvirt version: %v", err) 147 | } 148 | fmt.Println("Version:", v) 149 | 150 | flags := libvirt.ConnectListDomainsActive | libvirt.ConnectListDomainsInactive 151 | domains, _, err := l.ConnectListAllDomains(1, flags) 152 | if err != nil { 153 | log.Fatalf("failed to retrieve domains: %v", err) 154 | } 155 | 156 | fmt.Println("ID\tName\t\tUUID") 157 | fmt.Printf("--------------------------------------------------------\n") 158 | for _, d := range domains { 159 | fmt.Printf("%d\t%s\t%x\n", d.ID, d.Name, d.UUID) 160 | } 161 | 162 | if err = l.Disconnect(); err != nil { 163 | log.Fatalf("failed to disconnect: %v", err) 164 | } 165 | } 166 | 167 | 168 | ``` 169 | 170 | ``` 171 | Version: 1.3.4 172 | ID Name UUID 173 | -------------------------------------------------------- 174 | 1 Test-1 dc329f87d4de47198cfd2e21c6105b01 175 | 2 Test-2 dc229f87d4de47198cfd2e21c6105b01 176 | ``` 177 | 178 | Example (Connect to libvirt via TLS over TCP) 179 | ------- 180 | 181 | ```go 182 | package main 183 | 184 | import ( 185 | "crypto/tls" 186 | "crypto/x509" 187 | 188 | "fmt" 189 | "io/ioutil" 190 | "log" 191 | 192 | "github.com/digitalocean/go-libvirt" 193 | "github.com/digitalocean/go-libvirt/socket/dialers" 194 | ) 195 | 196 | func main() { 197 | // This dials libvirt on the local machine 198 | // It connects to libvirt via TLS over TCP 199 | // To connect to a remote machine, you need to have the ca/cert/key of it. 200 | // The private key is at ~/.pki/libvirt/clientkey.pem 201 | // or /etc/pki/libvirt/private/clientkey.pem 202 | // The Client Cert is at ~/.pki/libvirt/clientcert.pem 203 | // or /etc/pki/libvirt/clientcert.pem 204 | // The CA Cert is at ~/.pki/libvirt/cacert.pem 205 | // or /etc/pki/CA/cacert.pem 206 | 207 | // Use host name or IP which is valid in certificate 208 | addr := "10.10.10.10" 209 | 210 | l := libvirt.NewWithDialer(dialers.NewTLS(addr)) 211 | if err := l.Connect(); err != nil { 212 | log.Fatalf("failed to connect: %v", err) 213 | } 214 | 215 | v, err := l.Version() 216 | if err != nil { 217 | log.Fatalf("failed to retrieve libvirt version: %v", err) 218 | } 219 | fmt.Println("Version:", v) 220 | 221 | // Return both running and stopped VMs 222 | flags := libvirt.ConnectListDomainsActive | libvirt.ConnectListDomainsInactive 223 | domains, _, err := l.ConnectListAllDomains(1, flags) 224 | if err != nil { 225 | log.Fatalf("failed to retrieve domains: %v", err) 226 | } 227 | 228 | fmt.Println("ID\tName\t\tUUID") 229 | fmt.Println("--------------------------------------------------------") 230 | for _, d := range domains { 231 | fmt.Printf("%d\t%s\t%x\n", d.ID, d.Name, d.UUID) 232 | } 233 | 234 | if err := l.Disconnect(); err != nil { 235 | log.Fatalf("failed to disconnect: %v", err) 236 | } 237 | } 238 | ``` 239 | 240 | Running the Integration Tests 241 | ----------------------------- 242 | 243 | GitHub actions workflows are defined in [.github/workflows](.github/workflows) 244 | and can be triggered manually in the GitHub UI after pushing a branch. There 245 | are not currently convenient scripts for setting up and running integration tests 246 | locally, but installing libvirt and defining only the artifacts described by the 247 | files in testdata should be sufficient to be able to run the integration test file 248 | against. 249 | -------------------------------------------------------------------------------- /connect_uri.go: -------------------------------------------------------------------------------- 1 | package libvirt 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net/url" 7 | "os/user" 8 | "strconv" 9 | "strings" 10 | 11 | "github.com/digitalocean/go-libvirt/socket" 12 | "github.com/digitalocean/go-libvirt/socket/dialers" 13 | ) 14 | 15 | // ConnectToURI returns a new, connected client instance using the appropriate 16 | // dialer for the given libvirt URI. 17 | func ConnectToURI(uri *url.URL) (*Libvirt, error) { 18 | dialer, err := dialerForURI(uri) 19 | if err != nil { 20 | return nil, err 21 | } 22 | 23 | lv := NewWithDialer(dialer) 24 | 25 | if err := lv.ConnectToURI(RemoteURI(uri)); err != nil { 26 | return nil, fmt.Errorf("failed to connect to libvirt: %w", err) 27 | } 28 | 29 | return lv, nil 30 | } 31 | 32 | // RemoteURI returns the libvirtd URI corresponding to a given client URI. 33 | // The client URI contains details of the connection method, but once connected 34 | // to libvirtd, all connections are local. So e.g. the client may want to 35 | // connect to qemu+tcp://example.com/system but once the socket is established 36 | // it will ask the remote libvirtd for qemu:///system. 37 | func RemoteURI(uri *url.URL) ConnectURI { 38 | remoteURI := (&url.URL{ 39 | Scheme: strings.Split(uri.Scheme, "+")[0], 40 | Path: uri.Path, 41 | }).String() 42 | if name := uri.Query().Get("name"); name != "" { 43 | remoteURI = name 44 | } 45 | return ConnectURI(remoteURI) 46 | } 47 | 48 | func dialerForURI(uri *url.URL) (socket.Dialer, error) { 49 | transport := "unix" 50 | if scheme := strings.SplitN(uri.Scheme, "+", 2); len(scheme) > 1 { 51 | transport = scheme[1] 52 | } else if uri.Host != "" { 53 | transport = "tls" 54 | } 55 | 56 | switch transport { 57 | case "unix": 58 | options := []dialers.LocalOption{} 59 | if s := uri.Query().Get("socket"); s != "" { 60 | options = append(options, dialers.WithSocket(s)) 61 | } 62 | if err := checkModeOption(uri); err != nil { 63 | return nil, err 64 | } 65 | return dialers.NewLocal(options...), nil 66 | case "tcp": 67 | options := []dialers.RemoteOption{} 68 | if port := uri.Port(); port != "" { 69 | options = append(options, dialers.UsePort(port)) 70 | } 71 | return dialers.NewRemote(uri.Hostname(), options...), nil 72 | case "tls": 73 | options := []dialers.TLSOption{} 74 | if port := uri.Port(); port != "" { 75 | options = append(options, dialers.UseTLSPort(port)) 76 | } 77 | if pkiPath := uri.Query().Get("pkipath"); pkiPath != "" { 78 | options = append(options, dialers.UsePKIPath(pkiPath)) 79 | } 80 | if nv, err := noVerifyOption(uri); err != nil { 81 | return nil, err 82 | } else if nv { 83 | options = append(options, dialers.WithInsecureNoVerify()) 84 | } 85 | return dialers.NewTLS(uri.Hostname(), options...), nil 86 | case "libssh", "libssh2": 87 | options := []dialers.SSHOption{} 88 | options, err := processCommonSSHOptions(uri, options) 89 | if err != nil { 90 | return nil, err 91 | } 92 | if knownHosts := uri.Query().Get("known_hosts"); knownHosts != "" { 93 | options = append(options, dialers.UseKnownHostsFile(knownHosts)) 94 | } 95 | if hostVerify := uri.Query().Get("known_hosts_verify"); hostVerify != "" { 96 | switch hostVerify { 97 | case "normal": 98 | case "auto": 99 | options = append(options, dialers.WithAcceptUnknownHostKey()) 100 | case "ignore": 101 | options = append(options, dialers.WithInsecureIgnoreHostKey()) 102 | default: 103 | return nil, fmt.Errorf("invalid ssh known hosts verify method %v", hostVerify) 104 | } 105 | } 106 | if auth := uri.Query().Get("sshauth"); auth != "" { 107 | authMethods := &dialers.SSHAuthMethods{} 108 | for _, a := range strings.Split(auth, ",") { 109 | switch strings.ToLower(a) { 110 | case "agent": 111 | authMethods.Agent() 112 | case "privkey": 113 | authMethods.PrivKey() 114 | case "password": 115 | authMethods.Password() 116 | case "keyboard-interactive": 117 | authMethods.KeyboardInteractive() 118 | default: 119 | return nil, fmt.Errorf("invalid ssh auth method %v", a) 120 | } 121 | } 122 | options = append(options, dialers.WithSSHAuthMethods(authMethods)) 123 | } 124 | if noVerify := uri.Query().Get("no_verify"); noVerify != "" { 125 | return nil, fmt.Errorf( 126 | "\"no_verify\" option invalid with %s transport, use known_hosts_verify=ignore instead", 127 | transport) 128 | } 129 | return dialers.NewSSH(uri.Hostname(), options...), nil 130 | case "ssh": 131 | // Emulate ssh using golang ssh library. Note that this means that 132 | // system ssh config is not respected as it would be when shelling out 133 | // to the ssh binary. 134 | currentUser, err := user.Current() 135 | if err != nil { 136 | return nil, err 137 | } 138 | options := []dialers.SSHOption{ 139 | dialers.WithSystemSSHDefaults(currentUser), 140 | } 141 | options, err = processCommonSSHOptions(uri, options) 142 | if err != nil { 143 | return nil, err 144 | } 145 | if nv, err := noVerifyOption(uri); err != nil { 146 | return nil, err 147 | } else if nv { 148 | options = append(options, dialers.WithInsecureIgnoreHostKey()) 149 | } 150 | 151 | fieldErrs := []error{} 152 | for _, f := range []string{ 153 | "known_hosts", 154 | "known_hosts_verify", 155 | "sshauth", 156 | } { 157 | if field := uri.Query().Get(f); field != "" { 158 | fieldErrs = append(fieldErrs, 159 | fmt.Errorf("%v option invalid with ssh transport, use libssh transport instead", f)) 160 | } 161 | } 162 | if len(fieldErrs) > 0 { 163 | return nil, errors.Join(fieldErrs...) 164 | } 165 | 166 | return dialers.NewSSH(uri.Hostname(), options...), nil 167 | default: 168 | return nil, fmt.Errorf("unsupported libvirt transport %s", transport) 169 | } 170 | } 171 | 172 | func noVerifyOption(uri *url.URL) (bool, error) { 173 | nv := uri.Query().Get("no_verify") 174 | if nv == "" { 175 | return false, nil 176 | } 177 | val, err := strconv.Atoi(nv) 178 | if err != nil { 179 | return false, fmt.Errorf("invalid value for no_verify: %w", err) 180 | } 181 | return val != 0, nil 182 | } 183 | 184 | func checkModeOption(uri *url.URL) error { 185 | mode := uri.Query().Get("mode") 186 | switch strings.ToLower(mode) { 187 | case "": 188 | case "legacy", "auto": 189 | case "direct": 190 | return errors.New("cannot connect in direct mode") 191 | default: 192 | return fmt.Errorf("invalid ssh mode %v", mode) 193 | } 194 | return nil 195 | } 196 | 197 | func processCommonSSHOptions(uri *url.URL, options []dialers.SSHOption) ([]dialers.SSHOption, error) { 198 | if port := uri.Port(); port != "" { 199 | options = append(options, dialers.UseSSHPort(port)) 200 | } 201 | if username := uri.User.Username(); username != "" { 202 | options = append(options, dialers.UseSSHUsername(username)) 203 | } 204 | if password, ok := uri.User.Password(); ok { 205 | options = append(options, dialers.UseSSHPassword(password)) 206 | } 207 | if socket := uri.Query().Get("socket"); socket != "" { 208 | options = append(options, dialers.WithRemoteSocket(socket)) 209 | } 210 | if keyFile := uri.Query().Get("keyfile"); keyFile != "" { 211 | options = append(options, dialers.UseKeyFile(keyFile)) 212 | } 213 | if err := checkModeOption(uri); err != nil { 214 | return options, err 215 | } 216 | return options, nil 217 | } 218 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The go-libvirt Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package libvirt is a pure Go interface to libvirt. 16 | // 17 | // Rather than using Libvirt's C bindings, this package makes use of Libvirt's 18 | // RPC interface, as documented here: https://libvirt.org/internals/rpc.html. 19 | // Connections to the libvirt server may be local, or remote. RPC packets are 20 | // encoded using the XDR standard as defined by RFC 4506. 21 | // 22 | // Example usage: 23 | // 24 | // package main 25 | // 26 | // import ( 27 | // "fmt" 28 | // "log" 29 | // "net" 30 | // "net/url" 31 | // "time" 32 | // 33 | // "github.com/digitalocean/go-libvirt" 34 | // ) 35 | // 36 | // func main() { 37 | // uri, _ := url.Parse(string(libvirt.QEMUSystem)) 38 | // l, err := libvirt.ConnectToURI(uri) 39 | // if err != nil { 40 | // log.Fatalf("failed to connect: %v", err) 41 | // } 42 | // 43 | // v, err := l.Version() 44 | // if err != nil { 45 | // log.Fatalf("failed to retrieve libvirt version: %v", err) 46 | // } 47 | // fmt.Println("Version:", v) 48 | // 49 | // domains, err := l.Domains() 50 | // if err != nil { 51 | // log.Fatalf("failed to retrieve domains: %v", err) 52 | // } 53 | // 54 | // fmt.Println("ID\tName\t\tUUID") 55 | // fmt.Printf("--------------------------------------------------------\n") 56 | // for _, d := range domains { 57 | // fmt.Printf("%d\t%s\t%x\n", d.ID, d.Name, d.UUID) 58 | // } 59 | // 60 | // if err := l.Disconnect(); err != nil { 61 | // log.Fatalf("failed to disconnect: %v", err) 62 | // } 63 | // } 64 | package libvirt 65 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/digitalocean/go-libvirt 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.24.1 6 | 7 | require ( 8 | github.com/stretchr/testify v1.10.0 9 | golang.org/x/crypto v0.38.0 10 | golang.org/x/tools v0.33.0 11 | ) 12 | 13 | require ( 14 | github.com/davecgh/go-spew v1.1.1 // indirect 15 | github.com/pmezard/go-difflib v1.0.0 // indirect 16 | golang.org/x/sys v0.33.0 // indirect 17 | gopkg.in/yaml.v3 v3.0.1 // indirect 18 | ) 19 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 6 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 7 | golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= 8 | golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= 9 | golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= 10 | golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= 11 | golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= 12 | golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 13 | golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= 14 | golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 15 | golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= 16 | golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= 17 | golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= 18 | golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= 19 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 20 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 21 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 22 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 23 | -------------------------------------------------------------------------------- /internal/constants/qemu_protocol.gen.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The go-libvirt Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // 16 | // Code generated by internal/lvgen/generate.go. DO NOT EDIT. 17 | // 18 | // To regenerate, run 'go generate' in internal/lvgen. 19 | // 20 | 21 | package constants 22 | 23 | // These are libvirt procedure numbers which correspond to each respective 24 | // API call between remote_internal driver and libvirtd. Each procedure is 25 | // identified by a unique number. 26 | const ( 27 | // From enums: 28 | // QEMUProcDomainMonitorCommand is libvirt's QEMU_PROC_DOMAIN_MONITOR_COMMAND 29 | QEMUProcDomainMonitorCommand = 1 30 | // QEMUProcDomainAttach is libvirt's QEMU_PROC_DOMAIN_ATTACH 31 | QEMUProcDomainAttach = 2 32 | // QEMUProcDomainAgentCommand is libvirt's QEMU_PROC_DOMAIN_AGENT_COMMAND 33 | QEMUProcDomainAgentCommand = 3 34 | // QEMUProcConnectDomainMonitorEventRegister is libvirt's QEMU_PROC_CONNECT_DOMAIN_MONITOR_EVENT_REGISTER 35 | QEMUProcConnectDomainMonitorEventRegister = 4 36 | // QEMUProcConnectDomainMonitorEventDeregister is libvirt's QEMU_PROC_CONNECT_DOMAIN_MONITOR_EVENT_DEREGISTER 37 | QEMUProcConnectDomainMonitorEventDeregister = 5 38 | // QEMUProcDomainMonitorEvent is libvirt's QEMU_PROC_DOMAIN_MONITOR_EVENT 39 | QEMUProcDomainMonitorEvent = 6 40 | 41 | 42 | // From consts: 43 | // QEMUProgram is libvirt's QEMU_PROGRAM 44 | QEMUProgram = 0x20008087 45 | // QEMUProtocolVersion is libvirt's QEMU_PROTOCOL_VERSION 46 | QEMUProtocolVersion = 1 47 | ) 48 | -------------------------------------------------------------------------------- /internal/event/event.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The go-libvirt Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package event 16 | 17 | // Event represents an internal Event. 18 | type Event interface { 19 | GetCallbackID() int32 20 | } 21 | -------------------------------------------------------------------------------- /internal/event/stream.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The go-libvirt Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package event 16 | 17 | import ( 18 | "context" 19 | ) 20 | 21 | // emptyEvent is used as a zero-value. Clients will never receive one of these; 22 | // they are only here to satisfy the compiler. See the comments in process() for 23 | // more information. 24 | type emptyEvent struct{} 25 | 26 | func (emptyEvent) GetCallbackID() int32 { return 0 } 27 | 28 | // Stream is an unbounded buffered event channel. The implementation 29 | // consists of a pair of unbuffered channels and a goroutine to manage them. 30 | // Client behavior will not cause incoming events to block. 31 | type Stream struct { 32 | // Program specifies the source of the events - libvirt or QEMU. 33 | Program uint32 34 | 35 | // CallbackID is returned by the event registration call. 36 | CallbackID int32 37 | 38 | // manage unbounded channel behavior. 39 | queue []Event 40 | qlen chan (chan int) 41 | in, out chan Event 42 | 43 | // terminates processing 44 | shutdown context.CancelFunc 45 | } 46 | 47 | // NewStream configures a new Event Stream. Incoming events are appended to a 48 | // queue, which is then relayed to the listening client. Client behavior will 49 | // not cause incoming events to block. It is the responsibility of the caller 50 | // to terminate the Stream via Shutdown() when no longer in use. 51 | func NewStream(program uint32, cbID int32) *Stream { 52 | s := &Stream{ 53 | Program: program, 54 | CallbackID: cbID, 55 | in: make(chan Event), 56 | out: make(chan Event), 57 | qlen: make(chan (chan int)), 58 | } 59 | 60 | // Start the processing loop, which will return a routine we can use to 61 | // shut the queue down later. 62 | s.shutdown = s.start() 63 | 64 | return s 65 | } 66 | 67 | // Len will return the current count of events in the internal queue for a 68 | // stream. It does this by sending a message to the stream's process() loop, 69 | // which will then write the current length to the channel contained in that 70 | // message. 71 | func (s *Stream) Len() int { 72 | // Send a request to the process() loop to get the current length of the 73 | // queue 74 | ch := make(chan int) 75 | s.qlen <- ch 76 | return <-ch 77 | } 78 | 79 | // Recv returns the next available event from the Stream's queue. 80 | func (s *Stream) Recv() chan Event { 81 | return s.out 82 | } 83 | 84 | // Push appends a new event to the queue. 85 | func (s *Stream) Push(e Event) { 86 | s.in <- e 87 | } 88 | 89 | // Shutdown gracefully terminates Stream processing, releasing all internal 90 | // resources. Events which have not yet been received by the client will be 91 | // dropped. Subsequent calls to Shutdown() are idempotent. 92 | func (s *Stream) Shutdown() { 93 | if s.shutdown != nil { 94 | s.shutdown() 95 | } 96 | } 97 | 98 | // start starts the event processing loop, which will continue to run until 99 | // terminated by the returned context.CancelFunc. 100 | func (s *Stream) start() context.CancelFunc { 101 | ctx, cancel := context.WithCancel(context.Background()) 102 | 103 | go s.process(ctx) 104 | 105 | return cancel 106 | } 107 | 108 | // process manages an Stream's lifecycle until canceled by the provided context. 109 | // Incoming events are appended to a queue which is then relayed to the 110 | // listening client. New events pushed onto the queue will not block if the 111 | // client is not actively polling for them; the stream will buffer them 112 | // internally. 113 | func (s *Stream) process(ctx context.Context) { 114 | // Close the output channel so that clients know this stream is finished. 115 | // We don't close s.in to avoid creating a race with the stream's Push() 116 | // function. 117 | defer close(s.out) 118 | 119 | // This function is used to retrieve the next event from the queue, to be 120 | // sent to the client. If there are no more events to send, it returns a nil 121 | // channel and a zero-value event. 122 | nextEvent := func() (chan Event, Event) { 123 | sendCh := chan Event(nil) 124 | next := Event(emptyEvent{}) 125 | if len(s.queue) > 0 { 126 | sendCh = s.out 127 | next = s.queue[0] 128 | } 129 | return sendCh, next 130 | } 131 | 132 | // The select statement in this loop relies on the fact that a send to a nil 133 | // channel will block forever. If we have no entries in the queue, the 134 | // sendCh variable will be nil, so the clause that attempts to send an event 135 | // to the client will never complete. Clients will never receive an 136 | // emptyEvent. 137 | for { 138 | sendCh, nextEvt := nextEvent() 139 | 140 | select { 141 | // new event received, append to queue 142 | case e := <-s.in: 143 | s.queue = append(s.queue, e) 144 | 145 | case lenCh := <-s.qlen: 146 | lenCh <- len(s.queue) 147 | 148 | // client received an event, pop from queue 149 | case sendCh <- nextEvt: 150 | s.queue = s.queue[1:] 151 | 152 | // shutdown requested 153 | case <-ctx.Done(): 154 | return 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /internal/event/stream_test.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | type testEvent struct{ id int32 } 12 | 13 | func (e testEvent) GetCallbackID() int32 { 14 | return e.id 15 | } 16 | 17 | func testEvents(count int) []Event { 18 | ev := make([]Event, count) 19 | 20 | for i := 0; i < count; i++ { 21 | ev[i] = testEvent{int32(i)} 22 | } 23 | 24 | return ev 25 | } 26 | 27 | // TestStreamSequential tests the stream objects by first writing some entries 28 | // the stream, then reading them back. 29 | func TestStreamSequential(t *testing.T) { 30 | const evCount = 100000 31 | 32 | s := NewStream(1, 2) 33 | defer s.Shutdown() 34 | events := testEvents(evCount) 35 | 36 | // send a bunch of "events", and make sure we receive them all. 37 | fmt.Println("sending test events to queue") 38 | for i, e := range events { 39 | assert.Equal(t, int32(i), e.GetCallbackID()) 40 | s.Push(e) 41 | assert.Equal(t, i+1, s.Len()) 42 | } 43 | 44 | assert.Equal(t, evCount, s.Len()) 45 | 46 | fmt.Println("reading events from queue") 47 | for _, e := range events { 48 | qe, ok := <-s.Recv() 49 | assert.True(t, ok) 50 | assert.Equal(t, e, qe) 51 | } 52 | 53 | assert.Zero(t, s.Len()) 54 | } 55 | 56 | // TestStreamParallel tests the stream object with a pair of goroutines, one 57 | // writing to the queue, the other reading from it. 58 | func TestStreamParallel(t *testing.T) { 59 | const evCount = 10000 60 | 61 | s := NewStream(1, 2) 62 | defer s.Shutdown() 63 | events := testEvents(evCount) 64 | wg := sync.WaitGroup{} 65 | wg.Add(2) 66 | 67 | // Start 2 goroutines, one to send, the other to receive. 68 | go func() { 69 | defer wg.Done() 70 | for _, e := range events { 71 | s.Push(e) 72 | } 73 | }() 74 | 75 | go func() { 76 | defer wg.Done() 77 | for i := 0; i < evCount; i++ { 78 | e := <-s.Recv() 79 | assert.Equal(t, int32(i), e.GetCallbackID()) 80 | } 81 | }() 82 | 83 | wg.Wait() 84 | assert.Zero(t, s.Len()) 85 | } 86 | -------------------------------------------------------------------------------- /internal/go-xdr/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2014 Dave Collins 2 | 3 | Permission to use, copy, modify, and distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -------------------------------------------------------------------------------- /internal/go-xdr/README.md: -------------------------------------------------------------------------------- 1 | go-xdr 2 | ====== 3 | 4 | [![Build Status](https://travis-ci.org/davecgh/go-xdr.png?branch=master)] 5 | (https://travis-ci.org/davecgh/go-xdr) [![Coverage Status] 6 | (https://coveralls.io/repos/davecgh/go-xdr/badge.png?branch=master)] 7 | (https://coveralls.io/r/davecgh/go-xdr?branch=master) 8 | 9 | Go-xdr implements the data representation portion of the External Data 10 | Representation (XDR) standard protocol as specified in RFC 4506 (obsoletes RFC 11 | 1832 and RFC 1014) in Pure Go (Golang). A comprehensive suite of tests are 12 | provided to ensure proper functionality. It is licensed under the liberal ISC 13 | license, so it may be used in open source or commercial projects. 14 | 15 | NOTE: Version 1 of this package is still available via the 16 | github.com/davecgh/go-xdr/xdr import path to avoid breaking existing clients. However, it is highly recommended that all old clients upgrade to version 2 17 | and all new clients use version 2. In addition to some speed optimizations, 18 | version 2 has been been updated to work with standard the io.Reader and 19 | io.Writer interfaces instead of raw byte slices. This allows it to be much more 20 | flexible and work directly with files, network connections, etc. 21 | 22 | ## Documentation 23 | 24 | [![GoDoc](https://godoc.org/github.com/davecgh/go-xdr/xdr2?status.png)] 25 | (http://godoc.org/github.com/davecgh/go-xdr/xdr2) 26 | 27 | Full `go doc` style documentation for the project can be viewed online without 28 | installing this package by using the excellent GoDoc site here: 29 | http://godoc.org/github.com/davecgh/go-xdr/xdr2 30 | 31 | You can also view the documentation locally once the package is installed with 32 | the `godoc` tool by running `godoc -http=":6060"` and pointing your browser to 33 | http://localhost:6060/pkg/github.com/davecgh/go-xdr/xdr2/ 34 | 35 | ## Installation 36 | 37 | ```bash 38 | $ go get github.com/davecgh/go-xdr/xdr2 39 | ``` 40 | 41 | ## Sample Decode Program 42 | 43 | ```Go 44 | package main 45 | 46 | import ( 47 | "bytes" 48 | "fmt" 49 | 50 | "github.com/davecgh/go-xdr/xdr2" 51 | ) 52 | 53 | func main() { 54 | // Hypothetical image header format. 55 | type ImageHeader struct { 56 | Signature [3]byte 57 | Version uint32 58 | IsGrayscale bool 59 | NumSections uint32 60 | } 61 | 62 | // XDR encoded data described by the above structure. Typically this would 63 | // be read from a file or across the network, but use a manual byte array 64 | // here as an example. 65 | encodedData := []byte{ 66 | 0xAB, 0xCD, 0xEF, 0x00, // Signature 67 | 0x00, 0x00, 0x00, 0x02, // Version 68 | 0x00, 0x00, 0x00, 0x01, // IsGrayscale 69 | 0x00, 0x00, 0x00, 0x0A, // NumSections 70 | } 71 | 72 | // Declare a variable to provide Unmarshal with a concrete type and instance 73 | // to decode into. 74 | var h ImageHeader 75 | bytesRead, err := xdr.Unmarshal(bytes.NewReader(encodedData), &h) 76 | if err != nil { 77 | fmt.Println(err) 78 | return 79 | } 80 | 81 | fmt.Println("bytes read:", bytesRead) 82 | fmt.Printf("h: %+v", h) 83 | } 84 | ``` 85 | 86 | The struct instance, `h`, will then contain the following values: 87 | 88 | ```Go 89 | h.Signature = [3]byte{0xAB, 0xCD, 0xEF} 90 | h.Version = 2 91 | h.IsGrayscale = true 92 | h.NumSections = 10 93 | ``` 94 | 95 | ## Sample Encode Program 96 | 97 | ```Go 98 | package main 99 | 100 | import ( 101 | "bytes" 102 | "fmt" 103 | 104 | "github.com/davecgh/go-xdr/xdr2" 105 | ) 106 | 107 | func main() { 108 | // Hypothetical image header format. 109 | type ImageHeader struct { 110 | Signature [3]byte 111 | Version uint32 112 | IsGrayscale bool 113 | NumSections uint32 114 | } 115 | 116 | // Sample image header data. 117 | h := ImageHeader{[3]byte{0xAB, 0xCD, 0xEF}, 2, true, 10} 118 | 119 | // Use marshal to automatically determine the appropriate underlying XDR 120 | // types and encode. 121 | var w bytes.Buffer 122 | bytesWritten, err := xdr.Marshal(&w, &h) 123 | if err != nil { 124 | fmt.Println(err) 125 | return 126 | } 127 | 128 | encodedData := w.Bytes() 129 | fmt.Println("bytes written:", bytesWritten) 130 | fmt.Println("encoded data:", encodedData) 131 | } 132 | ``` 133 | 134 | The result, `encodedData`, will then contain the following XDR encoded byte 135 | sequence: 136 | 137 | ``` 138 | 0xAB, 0xCD, 0xEF, 0x00, 139 | 0x00, 0x00, 0x00, 0x02, 140 | 0x00, 0x00, 0x00, 0x01, 141 | 0x00, 0x00, 0x00, 0x0A, 142 | ``` 143 | 144 | ## License 145 | 146 | Go-xdr is licensed under the liberal ISC License. 147 | -------------------------------------------------------------------------------- /internal/go-xdr/goclean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # The script does automatic checking on a Go package and its sub-packages, including: 3 | # 1. gofmt (http://golang.org/cmd/gofmt/) 4 | # 2. goimports (https://github.com/bradfitz/goimports) 5 | # 3. golint (https://github.com/golang/lint) 6 | # 4. go vet (http://golang.org/cmd/vet) 7 | # 5. test coverage (http://blog.golang.org/cover) 8 | 9 | set -e 10 | 11 | # Automatic checks 12 | cd xdr2 13 | test -z "$(gofmt -l -w . | tee /dev/stderr)" 14 | test -z "$(goimports -l -w . | tee /dev/stderr)" 15 | test -z "$(golint . | tee /dev/stderr)" 16 | go vet ./... 17 | env GORACE="halt_on_error=1" go test -v -race ./... 18 | 19 | # Run test coverage on each subdirectories and merge the coverage profile. 20 | 21 | echo "mode: count" > profile.cov 22 | 23 | # Standard go tooling behavior is to ignore dirs with leading underscores. 24 | for dir in $(find . -maxdepth 10 -not -path './.git*' -not -path '*/_*' -type d); 25 | do 26 | if ls $dir/*.go &> /dev/null; then 27 | go test -covermode=count -coverprofile=$dir/profile.tmp $dir 28 | if [ -f $dir/profile.tmp ]; then 29 | cat $dir/profile.tmp | tail -n +2 >> profile.cov 30 | rm $dir/profile.tmp 31 | fi 32 | fi 33 | done 34 | 35 | go tool cover -func profile.cov 36 | 37 | # To submit the test coverage result to coveralls.io, 38 | # use goveralls (https://github.com/mattn/goveralls) 39 | # goveralls -coverprofile=profile.cov -service=travis-ci 40 | -------------------------------------------------------------------------------- /internal/go-xdr/xdr2/bench_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2014 Dave Collins 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | package xdr_test 18 | 19 | import ( 20 | "bytes" 21 | "testing" 22 | "unsafe" 23 | 24 | "github.com/digitalocean/go-libvirt/internal/go-xdr/xdr2" 25 | ) 26 | 27 | // BenchmarkUnmarshal benchmarks the Unmarshal function by using a dummy 28 | // ImageHeader structure. 29 | func BenchmarkUnmarshal(b *testing.B) { 30 | b.StopTimer() 31 | // Hypothetical image header format. 32 | type ImageHeader struct { 33 | Signature [3]byte 34 | Version uint32 35 | IsGrayscale bool 36 | NumSections uint32 37 | } 38 | // XDR encoded data described by the above structure. 39 | encodedData := []byte{ 40 | 0xAB, 0xCD, 0xEF, 0x00, 41 | 0x00, 0x00, 0x00, 0x02, 42 | 0x00, 0x00, 0x00, 0x01, 43 | 0x00, 0x00, 0x00, 0x0A, 44 | } 45 | var h ImageHeader 46 | b.StartTimer() 47 | 48 | for i := 0; i < b.N; i++ { 49 | r := bytes.NewReader(encodedData) 50 | _, _ = xdr.Unmarshal(r, &h) 51 | } 52 | b.SetBytes(int64(len(encodedData))) 53 | } 54 | 55 | // BenchmarkMarshal benchmarks the Marshal function by using a dummy ImageHeader 56 | // structure. 57 | func BenchmarkMarshal(b *testing.B) { 58 | b.StopTimer() 59 | // Hypothetical image header format. 60 | type ImageHeader struct { 61 | Signature [3]byte 62 | Version uint32 63 | IsGrayscale bool 64 | NumSections uint32 65 | } 66 | h := ImageHeader{[3]byte{0xAB, 0xCD, 0xEF}, 2, true, 10} 67 | size := unsafe.Sizeof(h) 68 | w := bytes.NewBuffer(nil) 69 | b.StartTimer() 70 | 71 | for i := 0; i < b.N; i++ { 72 | w.Reset() 73 | _, _ = xdr.Marshal(w, &h) 74 | } 75 | b.SetBytes(int64(size)) 76 | } 77 | -------------------------------------------------------------------------------- /internal/go-xdr/xdr2/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2014 Dave Collins 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | /* 18 | Package xdr implements the data representation portion of the External Data 19 | Representation (XDR) standard protocol as specified in RFC 4506 (obsoletes 20 | RFC 1832 and RFC 1014). 21 | 22 | The XDR RFC defines both a data specification language and a data 23 | representation standard. This package implements methods to encode and decode 24 | XDR data per the data representation standard with the exception of 128-bit 25 | quadruple-precision floating points. It does not currently implement parsing of 26 | the data specification language. In other words, the ability to automatically 27 | generate Go code by parsing an XDR data specification file (typically .x 28 | extension) is not supported. In practice, this limitation of the package is 29 | fairly minor since it is largely unnecessary due to the reflection capabilities 30 | of Go as described below. 31 | 32 | This package provides two approaches for encoding and decoding XDR data: 33 | 34 | 1) Marshal/Unmarshal functions which automatically map between XDR and Go types 35 | 2) Individual Encoder/Decoder objects to manually work with XDR primitives 36 | 37 | For the Marshal/Unmarshal functions, Go reflection capabilities are used to 38 | choose the type of the underlying XDR data based upon the Go type to encode or 39 | the target Go type to decode into. A description of how each type is mapped is 40 | provided below, however one important type worth reviewing is Go structs. In 41 | the case of structs, each exported field (first letter capitalized) is reflected 42 | and mapped in order. As a result, this means a Go struct with exported fields 43 | of the appropriate types listed in the expected order can be used to 44 | automatically encode / decode the XDR data thereby eliminating the need to write 45 | a lot of boilerplate code to encode/decode and error check each piece of XDR 46 | data as is typically required with C based XDR libraries. 47 | 48 | Go Type to XDR Type Mappings 49 | 50 | The following chart shows an overview of how Go types are mapped to XDR types 51 | for automatic marshalling and unmarshalling. The documentation for the Marshal 52 | and Unmarshal functions has specific details of how the mapping proceeds. 53 | 54 | Go Type <-> XDR Type 55 | -------------------- 56 | int8, int16, int32, int <-> XDR Integer 57 | uint8, uint16, uint32, uint <-> XDR Unsigned Integer 58 | int64 <-> XDR Hyper Integer 59 | uint64 <-> XDR Unsigned Hyper Integer 60 | bool <-> XDR Boolean 61 | float32 <-> XDR Floating-Point 62 | float64 <-> XDR Double-Precision Floating-Point 63 | string <-> XDR String 64 | byte <-> XDR Integer 65 | []byte <-> XDR Variable-Length Opaque Data 66 | [#]byte <-> XDR Fixed-Length Opaque Data 67 | [] <-> XDR Variable-Length Array 68 | [#] <-> XDR Fixed-Length Array 69 | struct <-> XDR Structure 70 | map <-> XDR Variable-Length Array of two-element XDR Structures 71 | time.Time <-> XDR String encoded with RFC3339 nanosecond precision 72 | 73 | Notes and Limitations: 74 | 75 | * Automatic marshalling and unmarshalling of variable and fixed-length 76 | arrays of uint8s require a special struct tag `xdropaque:"false"` 77 | since byte slices and byte arrays are assumed to be opaque data and 78 | byte is a Go alias for uint8 thus indistinguishable under reflection 79 | * Channel, complex, and function types cannot be encoded 80 | * Interfaces without a concrete value cannot be encoded 81 | * Cyclic data structures are not supported and will result in infinite 82 | loops 83 | * Strings are marshalled and unmarshalled with UTF-8 character encoding 84 | which differs from the XDR specification of ASCII, however UTF-8 is 85 | backwards compatible with ASCII so this should rarely cause issues 86 | 87 | 88 | Encoding 89 | 90 | To encode XDR data, use the Marshal function. 91 | func Marshal(w io.Writer, v interface{}) (int, error) 92 | 93 | For example, given the following code snippet: 94 | 95 | type ImageHeader struct { 96 | Signature [3]byte 97 | Version uint32 98 | IsGrayscale bool 99 | NumSections uint32 100 | } 101 | h := ImageHeader{[3]byte{0xAB, 0xCD, 0xEF}, 2, true, 10} 102 | 103 | var w bytes.Buffer 104 | bytesWritten, err := xdr.Marshal(&w, &h) 105 | // Error check elided 106 | 107 | The result, encodedData, will then contain the following XDR encoded byte 108 | sequence: 109 | 110 | 0xAB, 0xCD, 0xEF, 0x00, 111 | 0x00, 0x00, 0x00, 0x02, 112 | 0x00, 0x00, 0x00, 0x01, 113 | 0x00, 0x00, 0x00, 0x0A 114 | 115 | 116 | In addition, while the automatic marshalling discussed above will work for the 117 | vast majority of cases, an Encoder object is provided that can be used to 118 | manually encode XDR primitives for complex scenarios where automatic 119 | reflection-based encoding won't work. The included examples provide a sample of 120 | manual usage via an Encoder. 121 | 122 | 123 | Decoding 124 | 125 | To decode XDR data, use the Unmarshal function. 126 | func Unmarshal(r io.Reader, v interface{}) (int, error) 127 | 128 | For example, given the following code snippet: 129 | 130 | type ImageHeader struct { 131 | Signature [3]byte 132 | Version uint32 133 | IsGrayscale bool 134 | NumSections uint32 135 | } 136 | 137 | // Using output from the Encoding section above. 138 | encodedData := []byte{ 139 | 0xAB, 0xCD, 0xEF, 0x00, 140 | 0x00, 0x00, 0x00, 0x02, 141 | 0x00, 0x00, 0x00, 0x01, 142 | 0x00, 0x00, 0x00, 0x0A, 143 | } 144 | 145 | var h ImageHeader 146 | bytesRead, err := xdr.Unmarshal(bytes.NewReader(encodedData), &h) 147 | // Error check elided 148 | 149 | The struct instance, h, will then contain the following values: 150 | 151 | h.Signature = [3]byte{0xAB, 0xCD, 0xEF} 152 | h.Version = 2 153 | h.IsGrayscale = true 154 | h.NumSections = 10 155 | 156 | In addition, while the automatic unmarshalling discussed above will work for the 157 | vast majority of cases, a Decoder object is provided that can be used to 158 | manually decode XDR primitives for complex scenarios where automatic 159 | reflection-based decoding won't work. The included examples provide a sample of 160 | manual usage via a Decoder. 161 | 162 | Errors 163 | 164 | All errors are either of type UnmarshalError or MarshalError. Both provide 165 | human-readable output as well as an ErrorCode field which can be inspected by 166 | sophisticated callers if necessary. 167 | 168 | See the documentation of UnmarshalError, MarshalError, and ErrorCode for further 169 | details. 170 | */ 171 | package xdr 172 | -------------------------------------------------------------------------------- /internal/go-xdr/xdr2/error.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2014 Dave Collins 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | package xdr 18 | 19 | import "fmt" 20 | 21 | // ErrorCode identifies a kind of error. 22 | type ErrorCode int 23 | 24 | const ( 25 | // ErrBadArguments indicates arguments passed to the function are not 26 | // what was expected. 27 | ErrBadArguments ErrorCode = iota 28 | 29 | // ErrUnsupportedType indicates the Go type is not a supported type for 30 | // marshalling and unmarshalling XDR data. 31 | ErrUnsupportedType 32 | 33 | // ErrBadEnumValue indicates an enumeration value is not in the list of 34 | // valid values. 35 | ErrBadEnumValue 36 | 37 | // ErrNotSettable indicates an interface value cannot be written to. 38 | // This usually means the interface value was not passed with the & 39 | // operator, but it can also happen if automatic pointer allocation 40 | // fails. 41 | ErrNotSettable 42 | 43 | // ErrOverflow indicates that the data in question is too large to fit 44 | // into the corresponding Go or XDR data type. For example, an integer 45 | // decoded from XDR that is too large to fit into a target type of int8, 46 | // or opaque data that exceeds the max length of a Go slice. 47 | ErrOverflow 48 | 49 | // ErrNilInterface indicates an interface with no concrete type 50 | // information was encountered. Type information is necessary to 51 | // perform mapping between XDR and Go types. 52 | ErrNilInterface 53 | 54 | // ErrIO indicates an error was encountered while reading or writing to 55 | // an io.Reader or io.Writer, respectively. The actual underlying error 56 | // will be available via the Err field of the MarshalError or 57 | // UnmarshalError struct. 58 | ErrIO 59 | 60 | // ErrParseTime indicates an error was encountered while parsing an 61 | // RFC3339 formatted time value. The actual underlying error will be 62 | // available via the Err field of the UnmarshalError struct. 63 | ErrParseTime 64 | ) 65 | 66 | // Map of ErrorCode values back to their constant names for pretty printing. 67 | var errorCodeStrings = map[ErrorCode]string{ 68 | ErrBadArguments: "ErrBadArguments", 69 | ErrUnsupportedType: "ErrUnsupportedType", 70 | ErrBadEnumValue: "ErrBadEnumValue", 71 | ErrNotSettable: "ErrNotSettable", 72 | ErrOverflow: "ErrOverflow", 73 | ErrNilInterface: "ErrNilInterface", 74 | ErrIO: "ErrIO", 75 | ErrParseTime: "ErrParseTime", 76 | } 77 | 78 | // String returns the ErrorCode as a human-readable name. 79 | func (e ErrorCode) String() string { 80 | if s := errorCodeStrings[e]; s != "" { 81 | return s 82 | } 83 | return fmt.Sprintf("Unknown ErrorCode (%d)", e) 84 | } 85 | 86 | // UnmarshalError describes a problem encountered while unmarshaling data. 87 | // Some potential issues are unsupported Go types, attempting to decode a value 88 | // which is too large to fit into a specified Go type, and exceeding max slice 89 | // limitations. 90 | type UnmarshalError struct { 91 | ErrorCode ErrorCode // Describes the kind of error 92 | Func string // Function name 93 | Value interface{} // Value actually parsed where appropriate 94 | Description string // Human readable description of the issue 95 | Err error // The underlying error for IO errors 96 | } 97 | 98 | // Error satisfies the error interface and prints human-readable errors. 99 | func (e *UnmarshalError) Error() string { 100 | switch e.ErrorCode { 101 | case ErrBadEnumValue, ErrOverflow, ErrIO, ErrParseTime: 102 | return fmt.Sprintf("xdr:%s: %s - read: '%v'", e.Func, 103 | e.Description, e.Value) 104 | } 105 | return fmt.Sprintf("xdr:%s: %s", e.Func, e.Description) 106 | } 107 | 108 | // unmarshalError creates an error given a set of arguments and will copy byte 109 | // slices into the Value field since they might otherwise be changed from from 110 | // the original value. 111 | func unmarshalError(f string, c ErrorCode, desc string, v interface{}, err error) *UnmarshalError { 112 | e := &UnmarshalError{ErrorCode: c, Func: f, Description: desc, Err: err} 113 | switch t := v.(type) { 114 | case []byte: 115 | slice := make([]byte, len(t)) 116 | copy(slice, t) 117 | e.Value = slice 118 | default: 119 | e.Value = v 120 | } 121 | 122 | return e 123 | } 124 | 125 | // IsIO returns a boolean indicating whether the error is known to report that 126 | // the underlying reader or writer encountered an ErrIO. 127 | func IsIO(err error) bool { 128 | switch e := err.(type) { 129 | case *UnmarshalError: 130 | return e.ErrorCode == ErrIO 131 | case *MarshalError: 132 | return e.ErrorCode == ErrIO 133 | } 134 | return false 135 | } 136 | 137 | // MarshalError describes a problem encountered while marshaling data. 138 | // Some potential issues are unsupported Go types, attempting to encode more 139 | // opaque data than can be represented by a single opaque XDR entry, and 140 | // exceeding max slice limitations. 141 | type MarshalError struct { 142 | ErrorCode ErrorCode // Describes the kind of error 143 | Func string // Function name 144 | Value interface{} // Value actually parsed where appropriate 145 | Description string // Human readable description of the issue 146 | Err error // The underlying error for IO errors 147 | } 148 | 149 | // Error satisfies the error interface and prints human-readable errors. 150 | func (e *MarshalError) Error() string { 151 | switch e.ErrorCode { 152 | case ErrIO: 153 | return fmt.Sprintf("xdr:%s: %s - wrote: '%v'", e.Func, 154 | e.Description, e.Value) 155 | case ErrBadEnumValue: 156 | return fmt.Sprintf("xdr:%s: %s - value: '%v'", e.Func, 157 | e.Description, e.Value) 158 | } 159 | return fmt.Sprintf("xdr:%s: %s", e.Func, e.Description) 160 | } 161 | 162 | // marshalError creates an error given a set of arguments and will copy byte 163 | // slices into the Value field since they might otherwise be changed from from 164 | // the original value. 165 | func marshalError(f string, c ErrorCode, desc string, v interface{}, err error) *MarshalError { 166 | e := &MarshalError{ErrorCode: c, Func: f, Description: desc, Err: err} 167 | switch t := v.(type) { 168 | case []byte: 169 | slice := make([]byte, len(t)) 170 | copy(slice, t) 171 | e.Value = slice 172 | default: 173 | e.Value = v 174 | } 175 | 176 | return e 177 | } 178 | -------------------------------------------------------------------------------- /internal/go-xdr/xdr2/error_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Dave Collins 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | package xdr_test 18 | 19 | import ( 20 | "errors" 21 | "testing" 22 | 23 | . "github.com/digitalocean/go-libvirt/internal/go-xdr/xdr2" 24 | ) 25 | 26 | // TestErrorCodeStringer tests the stringized output for the ErrorCode type. 27 | func TestErrorCodeStringer(t *testing.T) { 28 | tests := []struct { 29 | in ErrorCode 30 | want string 31 | }{ 32 | {ErrBadArguments, "ErrBadArguments"}, 33 | {ErrUnsupportedType, "ErrUnsupportedType"}, 34 | {ErrBadEnumValue, "ErrBadEnumValue"}, 35 | {ErrNotSettable, "ErrNotSettable"}, 36 | {ErrOverflow, "ErrOverflow"}, 37 | {ErrNilInterface, "ErrNilInterface"}, 38 | {ErrIO, "ErrIO"}, 39 | {ErrParseTime, "ErrParseTime"}, 40 | {0xffff, "Unknown ErrorCode (65535)"}, 41 | } 42 | 43 | for i, test := range tests { 44 | result := test.in.String() 45 | if result != test.want { 46 | t.Errorf("String #%d\n got: %s want: %s", i, result, 47 | test.want) 48 | continue 49 | } 50 | } 51 | } 52 | 53 | // TestUnmarshalError tests the error output for the UnmarshalError type. 54 | func TestUnmarshalError(t *testing.T) { 55 | tests := []struct { 56 | in UnmarshalError 57 | want string 58 | }{ 59 | { 60 | UnmarshalError{ 61 | ErrorCode: ErrIO, 62 | Func: "test", 63 | Description: "EOF while decoding 5 bytes", 64 | Value: "testval", 65 | }, 66 | "xdr:test: EOF while decoding 5 bytes - read: 'testval'", 67 | }, 68 | { 69 | UnmarshalError{ 70 | ErrorCode: ErrBadEnumValue, 71 | Func: "test", 72 | Description: "invalid enum", 73 | Value: "testenum", 74 | }, 75 | "xdr:test: invalid enum - read: 'testenum'", 76 | }, 77 | { 78 | UnmarshalError{ 79 | ErrorCode: ErrNilInterface, 80 | Func: "test", 81 | Description: "can't unmarshal to nil interface", 82 | Value: nil, 83 | }, 84 | "xdr:test: can't unmarshal to nil interface", 85 | }, 86 | } 87 | 88 | for i, test := range tests { 89 | result := test.in.Error() 90 | if result != test.want { 91 | t.Errorf("Error #%d\n got: %s want: %s", i, result, 92 | test.want) 93 | continue 94 | } 95 | } 96 | } 97 | 98 | // TestMarshalError tests the error output for the MarshalError type. 99 | func TestMarshalError(t *testing.T) { 100 | tests := []struct { 101 | in MarshalError 102 | want string 103 | }{ 104 | { 105 | MarshalError{ 106 | ErrorCode: ErrIO, 107 | Func: "test", 108 | Description: "EOF while encoding 5 bytes", 109 | Value: []byte{0x01, 0x02}, 110 | }, 111 | "xdr:test: EOF while encoding 5 bytes - wrote: '[1 2]'", 112 | }, 113 | { 114 | MarshalError{ 115 | ErrorCode: ErrBadEnumValue, 116 | Func: "test", 117 | Description: "invalid enum", 118 | Value: "testenum", 119 | }, 120 | "xdr:test: invalid enum - value: 'testenum'", 121 | }, 122 | { 123 | MarshalError{ 124 | ErrorCode: ErrNilInterface, 125 | Func: "test", 126 | Description: "can't marshal to nil interface", 127 | Value: nil, 128 | }, 129 | "xdr:test: can't marshal to nil interface", 130 | }, 131 | } 132 | 133 | for i, test := range tests { 134 | result := test.in.Error() 135 | if result != test.want { 136 | t.Errorf("Error #%d\n got: %s want: %s", i, result, 137 | test.want) 138 | continue 139 | } 140 | } 141 | } 142 | 143 | // TestIOErr ensures the IsIO function behaves as expected given different error 144 | // types. 145 | func TestIOErr(t *testing.T) { 146 | tests := []struct { 147 | in error 148 | want bool 149 | }{ 150 | { 151 | &MarshalError{ 152 | ErrorCode: ErrIO, 153 | Func: "test", 154 | Description: "EOF while encoding 5 bytes", 155 | Value: []byte{0x01, 0x02}, 156 | }, 157 | true, 158 | }, 159 | { 160 | &MarshalError{ 161 | ErrorCode: ErrUnsupportedType, 162 | Func: "test", 163 | Description: "ErrUnsupportedType", 164 | Value: []byte{}, 165 | }, 166 | false, 167 | }, 168 | { 169 | &UnmarshalError{ 170 | ErrorCode: ErrIO, 171 | Func: "test", 172 | Description: "EOF while decoding 5 bytes", 173 | Value: []byte{0x01, 0x02}, 174 | }, 175 | true, 176 | }, 177 | { 178 | &UnmarshalError{ 179 | ErrorCode: ErrUnsupportedType, 180 | Func: "test", 181 | Description: "ErrUnsupportedType", 182 | Value: []byte{}, 183 | }, 184 | false, 185 | }, 186 | { 187 | errors.New("boom"), 188 | false, 189 | }, 190 | } 191 | 192 | for i, test := range tests { 193 | result := IsIO(test.in) 194 | if result != test.want { 195 | t.Errorf("Error #%d\n got: %v want: %v", i, result, 196 | test.want) 197 | continue 198 | } 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /internal/go-xdr/xdr2/example_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2014 Dave Collins 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | package xdr_test 18 | 19 | import ( 20 | "bytes" 21 | "fmt" 22 | 23 | "github.com/digitalocean/go-libvirt/internal/go-xdr/xdr2" 24 | ) 25 | 26 | // This example demonstrates how to use Marshal to automatically XDR encode 27 | // data using reflection. 28 | func ExampleMarshal() { 29 | // Hypothetical image header format. 30 | type ImageHeader struct { 31 | Signature [3]byte 32 | Version uint32 33 | IsGrayscale bool 34 | NumSections uint32 35 | } 36 | 37 | // Sample image header data. 38 | h := ImageHeader{[3]byte{0xAB, 0xCD, 0xEF}, 2, true, 10} 39 | 40 | // Use marshal to automatically determine the appropriate underlying XDR 41 | // types and encode. 42 | var w bytes.Buffer 43 | bytesWritten, err := xdr.Marshal(&w, &h) 44 | if err != nil { 45 | fmt.Println(err) 46 | return 47 | } 48 | 49 | fmt.Println("bytes written:", bytesWritten) 50 | fmt.Println("encoded data:", w.Bytes()) 51 | 52 | // Output: 53 | // bytes written: 16 54 | // encoded data: [171 205 239 0 0 0 0 2 0 0 0 1 0 0 0 10] 55 | } 56 | 57 | // This example demonstrates how to use Unmarshal to decode XDR encoded data 58 | // from a byte slice into a struct. 59 | func ExampleUnmarshal() { 60 | // Hypothetical image header format. 61 | type ImageHeader struct { 62 | Signature [3]byte 63 | Version uint32 64 | IsGrayscale bool 65 | NumSections uint32 66 | } 67 | 68 | // XDR encoded data described by the above structure. Typically this 69 | // would be read from a file or across the network, but use a manual 70 | // byte array here as an example. 71 | encodedData := []byte{ 72 | 0xAB, 0xCD, 0xEF, 0x00, // Signature 73 | 0x00, 0x00, 0x00, 0x02, // Version 74 | 0x00, 0x00, 0x00, 0x01, // IsGrayscale 75 | 0x00, 0x00, 0x00, 0x0A, // NumSections 76 | } 77 | 78 | // Declare a variable to provide Unmarshal with a concrete type and 79 | // instance to decode into. 80 | var h ImageHeader 81 | bytesRead, err := xdr.Unmarshal(bytes.NewReader(encodedData), &h) 82 | if err != nil { 83 | fmt.Println(err) 84 | return 85 | } 86 | 87 | fmt.Println("bytes read:", bytesRead) 88 | fmt.Printf("h: %+v", h) 89 | 90 | // Output: 91 | // bytes read: 16 92 | // h: {Signature:[171 205 239] Version:2 IsGrayscale:true NumSections:10} 93 | } 94 | 95 | // This example demonstrates how to manually decode XDR encoded data from a 96 | // reader. Compare this example with the Unmarshal example which performs the 97 | // same task automatically by utilizing a struct type definition and reflection. 98 | func ExampleNewDecoder() { 99 | // XDR encoded data for a hypothetical ImageHeader struct as follows: 100 | // type ImageHeader struct { 101 | // Signature [3]byte 102 | // Version uint32 103 | // IsGrayscale bool 104 | // NumSections uint32 105 | // } 106 | encodedData := []byte{ 107 | 0xAB, 0xCD, 0xEF, 0x00, // Signature 108 | 0x00, 0x00, 0x00, 0x02, // Version 109 | 0x00, 0x00, 0x00, 0x01, // IsGrayscale 110 | 0x00, 0x00, 0x00, 0x0A, // NumSections 111 | } 112 | 113 | // Get a new decoder for manual decoding. 114 | dec := xdr.NewDecoder(bytes.NewReader(encodedData)) 115 | 116 | signature, _, err := dec.DecodeFixedOpaque(3) 117 | if err != nil { 118 | fmt.Println(err) 119 | return 120 | } 121 | 122 | version, _, err := dec.DecodeUint() 123 | if err != nil { 124 | fmt.Println(err) 125 | return 126 | } 127 | 128 | isGrayscale, _, err := dec.DecodeBool() 129 | if err != nil { 130 | fmt.Println(err) 131 | return 132 | } 133 | 134 | numSections, _, err := dec.DecodeUint() 135 | if err != nil { 136 | fmt.Println(err) 137 | return 138 | } 139 | 140 | fmt.Println("signature:", signature) 141 | fmt.Println("version:", version) 142 | fmt.Println("isGrayscale:", isGrayscale) 143 | fmt.Println("numSections:", numSections) 144 | 145 | // Output: 146 | // signature: [171 205 239] 147 | // version: 2 148 | // isGrayscale: true 149 | // numSections: 10 150 | } 151 | 152 | // This example demonstrates how to manually encode XDR data from Go variables. 153 | // Compare this example with the Marshal example which performs the same task 154 | // automatically by utilizing a struct type definition and reflection. 155 | func ExampleNewEncoder() { 156 | // Data for a hypothetical ImageHeader struct as follows: 157 | // type ImageHeader struct { 158 | // Signature [3]byte 159 | // Version uint32 160 | // IsGrayscale bool 161 | // NumSections uint32 162 | // } 163 | signature := []byte{0xAB, 0xCD, 0xEF} 164 | version := uint32(2) 165 | isGrayscale := true 166 | numSections := uint32(10) 167 | 168 | // Get a new encoder for manual encoding. 169 | var w bytes.Buffer 170 | enc := xdr.NewEncoder(&w) 171 | 172 | _, err := enc.EncodeFixedOpaque(signature) 173 | if err != nil { 174 | fmt.Println(err) 175 | return 176 | } 177 | 178 | _, err = enc.EncodeUint(version) 179 | if err != nil { 180 | fmt.Println(err) 181 | return 182 | } 183 | 184 | _, err = enc.EncodeBool(isGrayscale) 185 | if err != nil { 186 | fmt.Println(err) 187 | return 188 | } 189 | 190 | _, err = enc.EncodeUint(numSections) 191 | if err != nil { 192 | fmt.Println(err) 193 | return 194 | } 195 | 196 | fmt.Println("encoded data:", w.Bytes()) 197 | 198 | // Output: 199 | // encoded data: [171 205 239 0 0 0 0 2 0 0 0 1 0 0 0 10] 200 | } 201 | -------------------------------------------------------------------------------- /internal/go-xdr/xdr2/fixedIO_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Dave Collins 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | package xdr_test 18 | 19 | import ( 20 | "io" 21 | ) 22 | 23 | // fixedWriter implements the io.Writer interface and intentially allows 24 | // testing of error paths by forcing short writes. 25 | type fixedWriter struct { 26 | b []byte 27 | pos int 28 | } 29 | 30 | // Write writes the contents of p to w. When the contents of p would cause 31 | // the writer to exceed the maximum allowed size of the fixed writer, the writer 32 | // writes as many bytes as possible to reach the maximum allowed size and 33 | // io.ErrShortWrite is returned. 34 | // 35 | // This satisfies the io.Writer interface. 36 | func (w *fixedWriter) Write(p []byte) (int, error) { 37 | if w.pos+len(p) > cap(w.b) { 38 | n := copy(w.b[w.pos:], p[:cap(w.b)-w.pos]) 39 | w.pos += n 40 | return n, io.ErrShortWrite 41 | } 42 | 43 | n := copy(w.b[w.pos:], p) 44 | w.pos += n 45 | return n, nil 46 | } 47 | 48 | // Bytes returns the bytes already written to the fixed writer. 49 | func (w *fixedWriter) Bytes() []byte { 50 | return w.b 51 | } 52 | 53 | // newFixedWriter returns a new io.Writer that will error once more bytes than 54 | // the specified max have been written. 55 | func newFixedWriter(max int) *fixedWriter { 56 | b := make([]byte, max, max) 57 | fw := fixedWriter{b, 0} 58 | return &fw 59 | } 60 | -------------------------------------------------------------------------------- /internal/go-xdr/xdr2/internal_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2014 Dave Collins 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | /* 18 | This test file is part of the xdr package rather than than the xdr_test package 19 | so it can bridge access to the internals to properly test cases which are either 20 | not possible or can't reliably be tested via the public interface. The functions 21 | are only exported while the tests are being run. 22 | */ 23 | 24 | package xdr 25 | 26 | import ( 27 | "io" 28 | "reflect" 29 | ) 30 | 31 | // TstEncode creates a new Encoder to the passed writer and returns the internal 32 | // encode function on the Encoder. 33 | func TstEncode(w io.Writer) func(v reflect.Value) (int, error) { 34 | enc := NewEncoder(w) 35 | return enc.encode 36 | } 37 | 38 | // TstDecode creates a new Decoder for the passed reader and returns the 39 | // internal decode function on the Decoder. 40 | func TstDecode(r io.Reader) func(v reflect.Value) (int, error) { 41 | dec := NewDecoder(r) 42 | return dec.decode 43 | } 44 | -------------------------------------------------------------------------------- /internal/lvgen/constants.tmpl: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The go-libvirt Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // 16 | // Code generated by internal/lvgen/generate.go. DO NOT EDIT. 17 | // 18 | // To regenerate, run 'go generate' in internal/lvgen. 19 | // 20 | 21 | package constants 22 | 23 | // These are libvirt procedure numbers which correspond to each respective 24 | // API call between remote_internal driver and libvirtd. Each procedure is 25 | // identified by a unique number. 26 | const ( 27 | // From enums: 28 | {{range .EnumVals}} // {{.Name}} is libvirt's {{.LVName}} 29 | {{.Name}} = {{.Val}} 30 | {{end}} 31 | 32 | // From consts: 33 | {{range .Consts}} // {{.Name}} is libvirt's {{.LVName}} 34 | {{.Name}} = {{.Val}} 35 | {{end -}} 36 | ) 37 | -------------------------------------------------------------------------------- /internal/lvgen/gen/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-libvirt Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | "path/filepath" 21 | "strings" 22 | 23 | "github.com/digitalocean/go-libvirt/internal/lvgen" 24 | ) 25 | 26 | var protoPaths = [...]string{ 27 | "src/remote/remote_protocol.x", 28 | "src/remote/qemu_protocol.x", 29 | } 30 | 31 | func main() { 32 | lvPath := os.Getenv("LIBVIRT_SOURCE") 33 | if lvPath == "" { 34 | fmt.Println("set $LIBVIRT_SOURCE to point to the root of the libvirt sources and retry") 35 | os.Exit(1) 36 | } 37 | fmt.Println("protocol file processing") 38 | for _, p := range protoPaths { 39 | protoPath := filepath.Join(lvPath, p) 40 | fmt.Println(" processing", p) 41 | err := processProto(protoPath) 42 | if err != nil { 43 | fmt.Println("go-libvirt code generator failed:", err) 44 | os.Exit(1) 45 | } 46 | } 47 | } 48 | 49 | func processProto(lvFile string) error { 50 | rdr, err := os.Open(lvFile) 51 | if err != nil { 52 | fmt.Printf("failed to open protocol file at %v: %v\n", lvFile, err) 53 | os.Exit(1) 54 | } 55 | defer rdr.Close() 56 | 57 | // extract the base filename, without extension, for the generator to use. 58 | name := strings.TrimSuffix(filepath.Base(lvFile), filepath.Ext(lvFile)) 59 | 60 | return lvgen.Generate(name, rdr) 61 | } 62 | -------------------------------------------------------------------------------- /internal/lvgen/lv-gen.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-libvirt Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package lvgen contains the instructions for regenerating the libvirt 16 | // bindings. We do this by parsing the remote_protocol.x file included in the 17 | // libvirt sources. Bindings will be generated if you run `go generate` in this 18 | // directory. 19 | package lvgen 20 | 21 | // Before running `go generate`: 22 | // 1) Make sure goyacc is installed from golang.org/x/tools (you can use this 23 | // command: `go get golang.org/x/tools/...`) 24 | // 2) Set the environment variable LIBVIRT_SOURCE to point to the top level 25 | // directory containing the version of libvirt for which you want to generate 26 | // bindings. 27 | 28 | //go:generate goyacc sunrpc.y 29 | //go:generate go run gen/main.go 30 | -------------------------------------------------------------------------------- /internal/lvgen/lvlexer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-libvirt Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package lvgen 16 | 17 | import ( 18 | "fmt" 19 | "io" 20 | "io/ioutil" 21 | "strings" 22 | "unicode" 23 | "unicode/utf8" 24 | ) 25 | 26 | // eof is returned by the lexer when there's no more input. 27 | const eof = -1 28 | 29 | // oneRuneTokens lists the runes the lexer will consider to be tokens when it 30 | // finds them. These are returned to the parser using the integer value of their 31 | // runes. 32 | var oneRuneTokens = `{}[]<>(),=;:*` 33 | 34 | var keywords = map[string]int{ 35 | "hyper": HYPER, 36 | "int": INT, 37 | "short": SHORT, 38 | "char": CHAR, 39 | "bool": BOOL, 40 | "case": CASE, 41 | "const": CONST, 42 | "default": DEFAULT, 43 | "double": DOUBLE, 44 | "enum": ENUM, 45 | "float": FLOAT, 46 | "opaque": OPAQUE, 47 | "string": STRING, 48 | "struct": STRUCT, 49 | "switch": SWITCH, 50 | "typedef": TYPEDEF, 51 | "union": UNION, 52 | "unsigned": UNSIGNED, 53 | "void": VOID, 54 | "program": PROGRAM, 55 | "version": VERSION, 56 | } 57 | 58 | // item is a lexeme, or what the lexer returns to the parser. 59 | type item struct { 60 | typ int 61 | val string 62 | line, column int 63 | } 64 | 65 | // String will display lexer items for humans to debug. There are some 66 | // calculations here due to the way goyacc arranges token values; see the 67 | // generated file y.go for an idea what's going on here, but the basic idea is 68 | // that the lower token type values are reserved for single-rune tokens, which 69 | // the lexer reports using the value of the rune itself. Everything else is 70 | // allocated a range of type value up above all the possible single-rune values. 71 | func (i item) String() string { 72 | tokType := i.typ 73 | if tokType >= yyPrivate { 74 | if tokType < yyPrivate+len(yyTok2) { 75 | tokType = int(yyTok2[tokType-yyPrivate]) 76 | } 77 | } 78 | rv := fmt.Sprintf("%s %q %d:%d", yyTokname(tokType), i.val, i.line, i.column) 79 | return rv 80 | } 81 | 82 | // Lexer stores the state of this lexer. 83 | type Lexer struct { 84 | input string // the string we're scanning. 85 | start int // start position of the item. 86 | pos int // current position in the input. 87 | line int // the current line (for error reporting). 88 | column int // current position within the current line. 89 | width int // width of the last rune scanned. 90 | items chan item // channel of scanned lexer items (lexemes). 91 | lastItem item // The last item the lexer handed the parser 92 | } 93 | 94 | // NewLexer will return a new lexer for the passed-in reader. 95 | func NewLexer(rdr io.Reader) (*Lexer, error) { 96 | l := &Lexer{} 97 | 98 | b, err := ioutil.ReadAll(rdr) 99 | if err != nil { 100 | return nil, err 101 | } 102 | l.input = string(b) 103 | l.items = make(chan item) 104 | 105 | return l, nil 106 | } 107 | 108 | // Run starts the lexer, and should be called in a goroutine. 109 | func (l *Lexer) Run() { 110 | for state := lexText; state != nil; { 111 | state = state(l) 112 | } 113 | close(l.items) 114 | } 115 | 116 | // emit returns a token to the parser. 117 | func (l *Lexer) emit(t int) { 118 | l.items <- item{t, l.input[l.start:l.pos], l.line, l.column} 119 | l.start = l.pos 120 | } 121 | 122 | // Lex gets the next token. 123 | func (l *Lexer) Lex(st *yySymType) int { 124 | s := <-l.items 125 | l.lastItem = s 126 | st.val = s.val 127 | return int(s.typ) 128 | } 129 | 130 | // Error is called by the parser when it finds a problem. 131 | func (l *Lexer) Error(s string) { 132 | fmt.Printf("parse error at %d:%d: %v\n", l.lastItem.line+1, l.lastItem.column+1, s) 133 | fmt.Printf("error at %q\n", l.lastItem.val) 134 | } 135 | 136 | // errorf is used by the lexer to report errors. It inserts an ERROR token into 137 | // the items channel, and sets the state to nil, which stops the lexer's state 138 | // machine. 139 | func (l *Lexer) errorf(format string, args ...interface{}) stateFn { 140 | l.items <- item{ERROR, fmt.Sprintf(format, args...), l.line, l.column} 141 | return nil 142 | } 143 | 144 | // next returns the rune at the current location, and advances to the next rune 145 | // in the input. 146 | func (l *Lexer) next() (r rune) { 147 | if l.pos >= len(l.input) { 148 | l.width = 0 149 | return eof 150 | } 151 | r, l.width = utf8.DecodeRuneInString(l.input[l.pos:]) 152 | l.pos += l.width 153 | l.column++ 154 | if r == '\n' { 155 | l.line++ 156 | l.column = 0 157 | } 158 | return r 159 | } 160 | 161 | // ignore discards the current text from start to pos. 162 | func (l *Lexer) ignore() { 163 | l.start = l.pos 164 | } 165 | 166 | // backup moves back one character, but can only be called once per next() call. 167 | func (l *Lexer) backup() { 168 | l.pos -= l.width 169 | if l.column > 0 { 170 | l.column-- 171 | } else { 172 | l.line-- 173 | } 174 | l.width = 0 175 | } 176 | 177 | // peek looks ahead at the next rune in the stream without consuming it. 178 | func (l *Lexer) peek() rune { 179 | r := l.next() 180 | l.backup() 181 | return r 182 | } 183 | 184 | // accept will advance to the next rune if it's contained in the string of valid 185 | // runes passed in by the caller. 186 | func (l *Lexer) accept(valid string) bool { 187 | if strings.IndexRune(valid, l.next()) >= 0 { 188 | return true 189 | } 190 | l.backup() 191 | return false 192 | } 193 | 194 | // acceptRun advances over a number of valid runes, stopping as soon as it hits 195 | // one not on the list. 196 | func (l *Lexer) acceptRun(valid string) { 197 | for strings.IndexRune(valid, l.next()) >= 0 { 198 | } 199 | l.backup() 200 | } 201 | 202 | // procIdent checks whether an identifier matches the pattern for a procedure 203 | // enum. 204 | func procIdent(ident string) bool { 205 | // The pattern we're looking for is "_PROC_", like 206 | // "REMOTE_PROC_DOMAIN_OPEN_CONSOLE" 207 | if ix := strings.Index(ident, "_PROC_"); ix != -1 { 208 | if strings.Index(ident[:ix], "_") == -1 { 209 | return true 210 | } 211 | } 212 | return false 213 | } 214 | 215 | // keyword checks whether the current lexeme is a keyword or not. If so it 216 | // returns the keyword's token id, otherwise it returns IDENTIFIER. 217 | func (l *Lexer) keyword() int { 218 | ident := l.input[l.start:l.pos] 219 | tok, ok := keywords[ident] 220 | if ok == true { 221 | return int(tok) 222 | } 223 | if procIdent(ident) { 224 | return PROCIDENTIFIER 225 | } 226 | return IDENTIFIER 227 | } 228 | 229 | // oneRuneToken determines whether a rune is a token. If so it returns the token 230 | // id and true, otherwise it returns false. 231 | func (l *Lexer) oneRuneToken(r rune) (int, bool) { 232 | if strings.IndexRune(oneRuneTokens, r) >= 0 { 233 | return int(r), true 234 | } 235 | 236 | return 0, false 237 | } 238 | 239 | // State functions 240 | type stateFn func(*Lexer) stateFn 241 | 242 | // lexText is the master lex routine. The lexer is started in this state. 243 | func lexText(l *Lexer) stateFn { 244 | for { 245 | if strings.HasPrefix(l.input[l.pos:], "/*") { 246 | return lexBlockComment 247 | } 248 | r := l.next() 249 | if r == eof { 250 | break 251 | } 252 | if unicode.IsSpace(r) { 253 | l.ignore() 254 | return lexText 255 | } 256 | if l.column == 1 && r == '%' { 257 | l.backup() 258 | return lexDirective 259 | } 260 | if unicode.IsLetter(r) { 261 | l.backup() 262 | return lexIdent 263 | } 264 | if unicode.IsNumber(r) || r == '-' { 265 | l.backup() 266 | return lexNumber 267 | } 268 | if t, isToken := l.oneRuneToken(r); isToken == true { 269 | l.emit(t) 270 | } 271 | } 272 | 273 | return nil 274 | } 275 | 276 | // lexBlockComment is used when we find a comment marker '/*' in the input. 277 | func lexBlockComment(l *Lexer) stateFn { 278 | // Double star is used only at the start of metadata comments 279 | metadataComment := strings.HasPrefix(l.input[l.pos:], "/**") 280 | for { 281 | if strings.HasPrefix(l.input[l.pos:], "*/") { 282 | // Found the end. Advance past the '*/' and discard the comment body 283 | // unless it's a metadata comment 284 | l.next() 285 | l.next() 286 | if metadataComment { 287 | l.emit(METADATACOMMENT) 288 | } else { 289 | l.ignore() 290 | } 291 | return lexText 292 | } 293 | if l.next() == eof { 294 | return l.errorf("unterminated block comment") 295 | } 296 | } 297 | } 298 | 299 | // lexIdent handles identifiers. 300 | func lexIdent(l *Lexer) stateFn { 301 | for { 302 | r := l.next() 303 | if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_' { 304 | continue 305 | } 306 | l.backup() 307 | break 308 | } 309 | // We may have a keyword, so check for that before emitting. 310 | l.emit(l.keyword()) 311 | 312 | return lexText 313 | } 314 | 315 | // lexNumber handles decimal and hexadecimal numbers. Decimal numbers may begin 316 | // with a '-'; hex numbers begin with '0x' and do not accept leading '-'. 317 | func lexNumber(l *Lexer) stateFn { 318 | // Leading '-' is ok 319 | digits := "0123456789" 320 | neg := l.accept("-") 321 | if !neg { 322 | // allow '0x' for hex numbers, as long as there's not a leading '-'. 323 | r := l.peek() 324 | if r == '0' { 325 | l.next() 326 | if l.accept("x") { 327 | digits = "0123456789ABCDEFabcdef" 328 | } 329 | } 330 | } 331 | // followed by any number of digits 332 | l.acceptRun(digits) 333 | r := l.peek() 334 | if unicode.IsLetter(r) { 335 | l.next() 336 | return l.errorf("invalid number: %q", l.input[l.start:l.pos]) 337 | } 338 | l.emit(CONSTANT) 339 | return lexText 340 | } 341 | 342 | // lexDirective handles lines beginning with '%'. These are used to emit C code 343 | // directly to the output file. For now we're ignoring them, but some of the 344 | // constants in the protocol file do depend on values from #included header 345 | // files, so that may need to change. 346 | func lexDirective(l *Lexer) stateFn { 347 | for { 348 | r := l.next() 349 | if r == '\n' { 350 | l.ignore() 351 | return lexText 352 | } 353 | if r == eof { 354 | return l.errorf("unterminated directive") 355 | } 356 | } 357 | } 358 | -------------------------------------------------------------------------------- /internal/lvgen/procedures.tmpl: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The go-libvirt Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // 16 | // Code generated by internal/lvgen/generate.go. DO NOT EDIT. 17 | // 18 | // To regenerate, run 'go generate' in internal/lvgen. 19 | // 20 | 21 | package libvirt 22 | 23 | import ( 24 | "bytes" 25 | "io" 26 | 27 | "github.com/digitalocean/go-libvirt/internal/constants" 28 | "github.com/digitalocean/go-libvirt/internal/go-xdr/xdr2" 29 | ) 30 | 31 | // References to prevent "imported and not used" errors. 32 | var ( 33 | _ = bytes.Buffer{} 34 | _ = io.Copy 35 | _ = constants.Program 36 | _ = xdr.Unmarshal 37 | ) 38 | 39 | // 40 | // Typedefs: 41 | // 42 | {{range .Typedefs}}// {{.Name}} is libvirt's {{.LVName}} 43 | type {{.Name}} {{.Type}} 44 | {{end}} 45 | // 46 | // Enums: 47 | // 48 | {{range .Enums}}// {{.Name}} is libvirt's {{.LVName}} 49 | type {{.Name}} {{.Type}} 50 | {{end}} 51 | // 52 | // Structs: 53 | // 54 | {{range .Structs}}// {{.Name}} is libvirt's {{.LVName}} 55 | type {{.Name}} struct { 56 | {{range .Members}} {{.Name}} {{.Type}} 57 | {{end -}} 58 | } 59 | 60 | {{end}} 61 | {{range .Unions}}// {{.Name}} is a discriminated union. 62 | type {{.Name}} struct { 63 | D uint32 64 | I interface{} 65 | } 66 | {{end -}} 67 | {{range .Unions}}{{$uname := .Name}}{{range .Cases}}{{$casetype := printf "%v%v" $uname .CaseName}} 68 | // New{{$casetype}} creates a discriminated union value satisfying 69 | // the {{$uname}} interface. 70 | func New{{$casetype}}(v {{.Type}}) *{{$uname}} { 71 | return &{{$uname}}{D: {{.DiscriminantVal}}, I: v} 72 | } 73 | {{end}} 74 | {{- end}} 75 | {{range $proc := .Procs}} 76 | // {{.Name}} is the go wrapper for {{.LVName}}. 77 | func (l *Libvirt) {{.Name}}( 78 | {{- range $ix, $arg := .Args}} 79 | {{- if (eq $ix $proc.WriteStreamIdx)}}{{if $ix}}, {{end}}outStream io.Reader{{end}} 80 | {{- if (eq $ix $proc.ReadStreamIdx)}}{{if $ix}}, {{end}}inStream io.Writer{{end}} 81 | {{- if $ix}}, {{end}}{{.Name}} {{.Type}} 82 | {{- end -}} 83 | ) ({{range .Ret}}r{{.Name}} {{.Type}}, {{end}}err error) { 84 | var buf []byte 85 | {{if .ArgsStruct}} 86 | args := {{.ArgsStruct}} { 87 | {{range .Args}} {{.Name}}: {{.Name}}, 88 | {{end}} } 89 | 90 | buf, err = encode(&args) 91 | if err != nil { 92 | return 93 | } 94 | {{end}} 95 | {{if .RetStruct}} var r response{{end}} 96 | {{if .RetStruct}}r{{else}}_{{end}}, err = l.requestStream({{.Num}}, constants.{{.Program}}Program, buf, 97 | {{- if (ne .WriteStreamIdx -1)}} outStream,{{else}} nil,{{end}} 98 | {{- if (ne .ReadStreamIdx -1)}} inStream{{else}} nil{{end -}} 99 | ) 100 | if err != nil { 101 | return 102 | } 103 | {{if .RetStruct}} 104 | // Return value unmarshaling 105 | tpd := typedParamDecoder{} 106 | ct := map[string]xdr.TypeDecoder{"libvirt.TypedParam": tpd} 107 | rdr := bytes.NewReader(r.Payload) 108 | dec := xdr.NewDecoderCustomTypes(rdr, 0, ct) 109 | {{range .Ret}} // {{.Name}}: {{.Type}} 110 | _, err = dec.Decode(&r{{.Name}}) 111 | if err != nil { 112 | return 113 | } 114 | {{end}}{{end}} 115 | return 116 | } 117 | {{end}} 118 | -------------------------------------------------------------------------------- /internal/lvgen/sunrpc.y: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-libvirt Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // The generated code notice below is output to the generated file. *This* file, 16 | // (sunrpc.y) is the yacc grammar for the sunrpc protocol language, and is *not* 17 | // generated. 18 | 19 | %{ 20 | // Copyright 2017 The go-libvirt Authors. 21 | // 22 | // Licensed under the Apache License, Version 2.0 (the "License"); 23 | // you may not use this file except in compliance with the License. 24 | // You may obtain a copy of the License at 25 | // 26 | // http://www.apache.org/licenses/LICENSE-2.0 27 | // 28 | // Unless required by applicable law or agreed to in writing, software 29 | // distributed under the License is distributed on an "AS IS" BASIS, 30 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 31 | // See the License for the specific language governing permissions and 32 | // limitations under the License. 33 | 34 | // 35 | // Code generated by goyacc. DO NOT EDIT. 36 | // 37 | // To regenerate, run 'go generate' in internal/lvgen. 38 | // 39 | 40 | package lvgen 41 | 42 | import ( 43 | //"fmt" 44 | ) 45 | 46 | %} 47 | 48 | // SymType 49 | %union{ 50 | val string 51 | } 52 | 53 | // XDR tokens: 54 | %token BOOL CASE CONST DEFAULT DOUBLE ENUM FLOAT OPAQUE STRING STRUCT 55 | %token SWITCH TYPEDEF UNION UNSIGNED VOID HYPER INT SHORT CHAR 56 | %token IDENTIFIER CONSTANT ERROR 57 | // RPCL additional tokens: 58 | %token PROGRAM VERSION 59 | %token METADATACOMMENT 60 | %token PROCIDENTIFIER 61 | 62 | %% 63 | 64 | specification 65 | : definition_list 66 | ; 67 | 68 | value 69 | : IDENTIFIER 70 | | CONSTANT 71 | ; 72 | 73 | definition_list 74 | : definition ';' 75 | | definition ';' definition_list 76 | ; 77 | 78 | definition 79 | : enum_definition 80 | | const_definition 81 | | typedef_definition 82 | | struct_definition 83 | | union_definition 84 | | program_definition 85 | ; 86 | 87 | enum_definition 88 | : ENUM enum_ident '{' enum_value_list '}' { StartEnum($2.val) } 89 | ; 90 | 91 | enum_value_list 92 | : enum_value 93 | | enum_value ',' enum_value_list 94 | ; 95 | 96 | enum_value 97 | : enum_value_ident { 98 | err := AddEnumAutoVal($1.val) 99 | if err != nil { 100 | yylex.Error(err.Error()) 101 | return 1 102 | } 103 | } 104 | | enum_value_ident '=' value { 105 | err := AddEnumVal($1.val, $3.val) 106 | if err != nil { 107 | yylex.Error(err.Error()) 108 | return 1 109 | } 110 | } 111 | | enum_proc_ident '=' value { 112 | err := AddProcEnumVal($1.val, $3.val, "") 113 | if err != nil { 114 | yylex.Error(err.Error()) 115 | return 1 116 | } 117 | } 118 | | METADATACOMMENT enum_proc_ident '=' value { 119 | err := AddProcEnumVal($2.val, $4.val, $1.val) 120 | if err != nil { 121 | yylex.Error(err.Error()) 122 | return 1 123 | } 124 | } 125 | ; 126 | 127 | enum_proc_ident 128 | : PROCIDENTIFIER 129 | ; 130 | 131 | enum_ident 132 | : IDENTIFIER 133 | ; 134 | 135 | enum_value_ident 136 | : IDENTIFIER 137 | ; 138 | 139 | // Ignore consts that are set to IDENTIFIERs - this isn't allowed by the spec, 140 | // but occurs in the file because libvirt runs the pre-processor on the protocol 141 | // file, and it handles replacing the identifier with it's #defined value. 142 | const_definition 143 | : CONST const_ident '=' IDENTIFIER 144 | | CONST const_ident '=' CONSTANT { 145 | err := AddConst($2.val, $4.val) 146 | if err != nil { 147 | yylex.Error(err.Error()) 148 | return 1 149 | } 150 | } 151 | ; 152 | 153 | const_ident 154 | : IDENTIFIER 155 | ; 156 | 157 | typedef_definition 158 | : TYPEDEF {StartTypedef()} declaration 159 | ; 160 | 161 | declaration 162 | : simple_declaration 163 | | fixed_array_declaration 164 | | variable_array_declaration 165 | | pointer_declaration 166 | ; 167 | 168 | simple_declaration 169 | : type_specifier variable_ident {AddDeclaration($2.val, $1.val)} 170 | ; 171 | 172 | type_specifier 173 | : int_spec 174 | | UNSIGNED int_spec {$$.val = "u"+$2.val} 175 | | FLOAT {$$.val = "float32"} 176 | | DOUBLE {$$.val = "float64"} 177 | | BOOL {$$.val = "bool"} 178 | | STRING {$$.val = "string"} 179 | | OPAQUE {$$.val = "byte"} 180 | | enum_definition 181 | | struct_definition 182 | | union_definition 183 | | IDENTIFIER 184 | ; 185 | 186 | int_spec 187 | : HYPER {$$.val = "int64"} 188 | | INT {$$.val = "int32"} 189 | | SHORT {$$.val = "int16"} 190 | | CHAR {$$.val = "int8"} 191 | ; 192 | 193 | variable_ident 194 | : IDENTIFIER 195 | ; 196 | 197 | fixed_array_declaration 198 | : type_specifier variable_ident '[' value ']' { AddFixedArray($2.val, $1.val, $4.val) } 199 | ; 200 | 201 | variable_array_declaration 202 | : type_specifier variable_ident '<' value '>' { AddVariableArray($2.val, $1.val, $4.val) } 203 | | type_specifier variable_ident '<' '>' { AddVariableArray($2.val, $1.val, "") } 204 | ; 205 | 206 | // while pointer_declarations may look like their familiar c-equivalents, in the 207 | // XDR language they actually declare "Optional-data". The simplest 208 | // representation to use for these is a variable-length array with a size of 1. 209 | // See the XDR spec for a more complete explanation of this. 210 | pointer_declaration 211 | : type_specifier '*' variable_ident { AddOptValue($3.val, $1.val) } 212 | ; 213 | 214 | struct_definition 215 | : STRUCT struct_ident '{' {StartStruct($2.val)} declaration_list '}' { AddStruct() } 216 | ; 217 | 218 | struct_ident 219 | : IDENTIFIER 220 | ; 221 | 222 | declaration_list 223 | : declaration ';' 224 | | declaration ';' declaration_list 225 | ; 226 | 227 | union_definition 228 | : UNION union_ident {StartUnion($2.val)} SWITCH '(' simple_declaration ')' '{' case_list '}' {AddUnion()} 229 | ; 230 | 231 | union_ident 232 | : IDENTIFIER 233 | ; 234 | 235 | case_list 236 | : case ';' 237 | | case ';' case_list 238 | ; 239 | 240 | case 241 | : CASE value {StartCase($2.val)} ':' declaration {AddCase()} 242 | | DEFAULT {StartCase("default")} ':' declaration {AddCase()} 243 | ; 244 | 245 | program_definition 246 | : PROGRAM program_ident '{' version_list '}' '=' value 247 | ; 248 | 249 | program_ident 250 | : IDENTIFIER 251 | ; 252 | 253 | version_list 254 | : version ';' 255 | | version ';' version_list 256 | ; 257 | 258 | version 259 | : VERSION version_ident '{' procedure_list '}' '=' value ';' 260 | ; 261 | 262 | version_ident 263 | : IDENTIFIER 264 | ; 265 | 266 | procedure_list 267 | : procedure ';' 268 | | procedure ';' procedure_list 269 | ; 270 | 271 | procedure 272 | : type_specifier procedure_ident '(' type_specifier ')' '=' value ';' 273 | ; 274 | 275 | procedure_ident 276 | : IDENTIFIER 277 | ; 278 | 279 | %% 280 | -------------------------------------------------------------------------------- /internal/lvgen/y.go: -------------------------------------------------------------------------------- 1 | // Code generated by goyacc sunrpc.y. DO NOT EDIT. 2 | 3 | //line sunrpc.y:20 4 | // Copyright 2017 The go-libvirt Authors. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | // 19 | // Code generated by goyacc. DO NOT EDIT. 20 | // 21 | // To regenerate, run 'go generate' in internal/lvgen. 22 | // 23 | 24 | package lvgen 25 | 26 | import __yyfmt__ "fmt" 27 | 28 | //line sunrpc.y:40 29 | 30 | import ( 31 | //"fmt" 32 | ) 33 | 34 | //line sunrpc.y:49 35 | type yySymType struct { 36 | yys int 37 | val string 38 | } 39 | 40 | const BOOL = 57346 41 | const CASE = 57347 42 | const CONST = 57348 43 | const DEFAULT = 57349 44 | const DOUBLE = 57350 45 | const ENUM = 57351 46 | const FLOAT = 57352 47 | const OPAQUE = 57353 48 | const STRING = 57354 49 | const STRUCT = 57355 50 | const SWITCH = 57356 51 | const TYPEDEF = 57357 52 | const UNION = 57358 53 | const UNSIGNED = 57359 54 | const VOID = 57360 55 | const HYPER = 57361 56 | const INT = 57362 57 | const SHORT = 57363 58 | const CHAR = 57364 59 | const IDENTIFIER = 57365 60 | const CONSTANT = 57366 61 | const ERROR = 57367 62 | const PROGRAM = 57368 63 | const VERSION = 57369 64 | const METADATACOMMENT = 57370 65 | const PROCIDENTIFIER = 57371 66 | 67 | var yyToknames = [...]string{ 68 | "$end", 69 | "error", 70 | "$unk", 71 | "BOOL", 72 | "CASE", 73 | "CONST", 74 | "DEFAULT", 75 | "DOUBLE", 76 | "ENUM", 77 | "FLOAT", 78 | "OPAQUE", 79 | "STRING", 80 | "STRUCT", 81 | "SWITCH", 82 | "TYPEDEF", 83 | "UNION", 84 | "UNSIGNED", 85 | "VOID", 86 | "HYPER", 87 | "INT", 88 | "SHORT", 89 | "CHAR", 90 | "IDENTIFIER", 91 | "CONSTANT", 92 | "ERROR", 93 | "PROGRAM", 94 | "VERSION", 95 | "METADATACOMMENT", 96 | "PROCIDENTIFIER", 97 | "';'", 98 | "'{'", 99 | "'}'", 100 | "','", 101 | "'='", 102 | "'['", 103 | "']'", 104 | "'<'", 105 | "'>'", 106 | "'*'", 107 | "'('", 108 | "')'", 109 | "':'", 110 | } 111 | 112 | var yyStatenames = [...]string{} 113 | 114 | const yyEofCode = 1 115 | const yyErrCode = 2 116 | const yyInitialStackSize = 16 117 | 118 | //line sunrpc.y:279 119 | 120 | //line yacctab:1 121 | var yyExca = [...]int8{ 122 | -1, 1, 123 | 1, -1, 124 | -2, 0, 125 | } 126 | 127 | const yyPrivate = 57344 128 | 129 | const yyLast = 157 130 | 131 | var yyAct = [...]uint8{ 132 | 89, 82, 36, 119, 111, 81, 64, 70, 32, 55, 133 | 58, 137, 134, 136, 108, 125, 90, 91, 83, 66, 134 | 37, 106, 78, 31, 79, 105, 139, 61, 123, 101, 135 | 74, 96, 41, 93, 76, 65, 40, 10, 39, 43, 136 | 42, 13, 75, 30, 14, 38, 126, 48, 49, 50, 137 | 51, 47, 115, 97, 84, 73, 114, 103, 60, 67, 138 | 54, 52, 29, 59, 61, 118, 142, 135, 127, 116, 139 | 77, 98, 80, 85, 16, 72, 66, 92, 11, 94, 140 | 95, 10, 90, 91, 88, 13, 100, 12, 14, 62, 141 | 63, 87, 99, 102, 104, 69, 27, 25, 15, 23, 142 | 20, 18, 110, 2, 107, 117, 113, 109, 48, 49, 143 | 50, 51, 46, 8, 112, 45, 7, 44, 4, 113, 144 | 28, 124, 128, 121, 130, 122, 86, 71, 131, 8, 145 | 26, 132, 7, 129, 4, 133, 138, 120, 53, 140, 146 | 141, 24, 68, 22, 35, 34, 33, 21, 19, 57, 147 | 56, 17, 9, 6, 5, 3, 1, 148 | } 149 | 150 | var yyPact = [...]int16{ 151 | 72, -1000, -1000, 44, -1000, -1000, -1000, -1000, -1000, -1000, 152 | 78, 77, -1000, 76, 74, 73, 72, 31, -1000, 9, 153 | -1000, 28, 30, -1000, -1000, -1000, 29, -1000, -1000, 35, 154 | 66, -1000, -1000, -1000, -1000, -1000, -4, -1000, 89, -1000, 155 | -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, 156 | -1000, -1000, -1000, 81, 48, 23, -3, 8, 0, -2, 157 | -1000, -1000, -1000, -1000, -13, 53, -1000, -1000, 28, -22, 158 | 22, 43, 68, -1000, 35, 59, 59, -1, 59, -7, 159 | -1000, 21, 41, 28, -5, 48, 26, -1000, -1000, -1000, 160 | -1000, -1000, -1000, 59, -11, -17, -1000, -1000, 28, -27, 161 | 53, 59, -1000, 28, -1000, -1000, -1000, -1000, 25, -1000, 162 | -1000, 20, 39, 42, 118, -6, 28, -25, -1000, 14, 163 | 38, 59, -1000, 59, -1000, 28, -1000, 118, -1000, -30, 164 | 37, -28, -1000, -31, 28, -1000, -8, 28, -1000, 59, 165 | -1000, 36, -1000, 166 | } 167 | 168 | var yyPgo = [...]uint8{ 169 | 0, 156, 103, 0, 155, 117, 154, 153, 115, 112, 170 | 152, 151, 9, 150, 149, 10, 148, 147, 1, 8, 171 | 146, 145, 144, 2, 6, 20, 143, 142, 5, 141, 172 | 138, 3, 137, 135, 133, 130, 7, 127, 126, 4, 173 | 114, 105, 174 | } 175 | 176 | var yyR1 = [...]int8{ 177 | 0, 1, 3, 3, 2, 2, 4, 4, 4, 4, 178 | 4, 4, 5, 12, 12, 13, 13, 13, 13, 15, 179 | 11, 14, 6, 6, 16, 17, 7, 18, 18, 18, 180 | 18, 19, 23, 23, 23, 23, 23, 23, 23, 23, 181 | 23, 23, 23, 25, 25, 25, 25, 24, 20, 21, 182 | 21, 22, 27, 8, 26, 28, 28, 30, 9, 29, 183 | 31, 31, 33, 32, 34, 32, 10, 35, 36, 36, 184 | 37, 38, 39, 39, 40, 41, 185 | } 186 | 187 | var yyR2 = [...]int8{ 188 | 0, 1, 1, 1, 2, 3, 1, 1, 1, 1, 189 | 1, 1, 5, 1, 3, 1, 3, 3, 4, 1, 190 | 1, 1, 4, 4, 1, 0, 3, 1, 1, 1, 191 | 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 192 | 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 193 | 4, 3, 0, 6, 1, 2, 3, 0, 10, 1, 194 | 2, 3, 0, 5, 0, 4, 7, 1, 2, 3, 195 | 8, 1, 2, 3, 8, 1, 196 | } 197 | 198 | var yyChk = [...]int16{ 199 | -1000, -1, -2, -4, -5, -6, -7, -8, -9, -10, 200 | 9, 6, 15, 13, 16, 26, 30, -11, 23, -16, 201 | 23, -17, -26, 23, -29, 23, -35, 23, -2, 31, 202 | 34, -18, -19, -20, -21, -22, -23, -25, 17, 10, 203 | 8, 4, 12, 11, -5, -8, -9, 23, 19, 20, 204 | 21, 22, 31, -30, 31, -12, -13, -14, -15, 28, 205 | 23, 29, 23, 24, -24, 39, 23, -25, -27, 14, 206 | -36, -37, 27, 32, 33, 34, 34, -15, 35, 37, 207 | -24, -28, -18, 40, 32, 30, -38, 23, -12, -3, 208 | 23, 24, -3, 34, -3, -3, 38, 32, 30, -19, 209 | -23, 34, -36, 31, -3, 36, 38, -28, 41, -24, 210 | -3, -39, -40, -23, 31, 32, 30, -41, 23, -31, 211 | -32, 5, 7, 34, -39, 40, 32, 30, -3, -34, 212 | -3, -23, -31, -33, 42, 30, 41, 42, -18, 34, 213 | -18, -3, 30, 214 | } 215 | 216 | var yyDef = [...]int8{ 217 | 0, -2, 1, 0, 6, 7, 8, 9, 10, 11, 218 | 0, 0, 25, 0, 0, 0, 4, 0, 20, 0, 219 | 24, 0, 0, 54, 57, 59, 0, 67, 5, 0, 220 | 0, 26, 27, 28, 29, 30, 0, 32, 0, 34, 221 | 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 222 | 45, 46, 52, 0, 0, 0, 13, 15, 0, 0, 223 | 21, 19, 22, 23, 31, 0, 47, 33, 0, 0, 224 | 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 225 | 51, 0, 0, 0, 0, 68, 0, 71, 14, 16, 226 | 2, 3, 17, 0, 0, 0, 50, 53, 55, 0, 227 | 0, 0, 69, 0, 18, 48, 49, 56, 0, 31, 228 | 66, 0, 0, 0, 0, 0, 72, 0, 75, 0, 229 | 0, 0, 64, 0, 73, 0, 58, 60, 62, 0, 230 | 0, 0, 61, 0, 0, 70, 0, 0, 65, 0, 231 | 63, 0, 74, 232 | } 233 | 234 | var yyTok1 = [...]int8{ 235 | 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 236 | 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 237 | 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 238 | 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 239 | 40, 41, 39, 3, 33, 3, 3, 3, 3, 3, 240 | 3, 3, 3, 3, 3, 3, 3, 3, 42, 30, 241 | 37, 34, 38, 3, 3, 3, 3, 3, 3, 3, 242 | 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 243 | 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 244 | 3, 35, 3, 36, 3, 3, 3, 3, 3, 3, 245 | 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 246 | 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 247 | 3, 3, 3, 31, 3, 32, 248 | } 249 | 250 | var yyTok2 = [...]int8{ 251 | 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 252 | 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 253 | 22, 23, 24, 25, 26, 27, 28, 29, 254 | } 255 | 256 | var yyTok3 = [...]int8{ 257 | 0, 258 | } 259 | 260 | var yyErrorMessages = [...]struct { 261 | state int 262 | token int 263 | msg string 264 | }{} 265 | 266 | //line yaccpar:1 267 | 268 | /* parser for yacc output */ 269 | 270 | var ( 271 | yyDebug = 0 272 | yyErrorVerbose = false 273 | ) 274 | 275 | type yyLexer interface { 276 | Lex(lval *yySymType) int 277 | Error(s string) 278 | } 279 | 280 | type yyParser interface { 281 | Parse(yyLexer) int 282 | Lookahead() int 283 | } 284 | 285 | type yyParserImpl struct { 286 | lval yySymType 287 | stack [yyInitialStackSize]yySymType 288 | char int 289 | } 290 | 291 | func (p *yyParserImpl) Lookahead() int { 292 | return p.char 293 | } 294 | 295 | func yyNewParser() yyParser { 296 | return &yyParserImpl{} 297 | } 298 | 299 | const yyFlag = -1000 300 | 301 | func yyTokname(c int) string { 302 | if c >= 1 && c-1 < len(yyToknames) { 303 | if yyToknames[c-1] != "" { 304 | return yyToknames[c-1] 305 | } 306 | } 307 | return __yyfmt__.Sprintf("tok-%v", c) 308 | } 309 | 310 | func yyStatname(s int) string { 311 | if s >= 0 && s < len(yyStatenames) { 312 | if yyStatenames[s] != "" { 313 | return yyStatenames[s] 314 | } 315 | } 316 | return __yyfmt__.Sprintf("state-%v", s) 317 | } 318 | 319 | func yyErrorMessage(state, lookAhead int) string { 320 | const TOKSTART = 4 321 | 322 | if !yyErrorVerbose { 323 | return "syntax error" 324 | } 325 | 326 | for _, e := range yyErrorMessages { 327 | if e.state == state && e.token == lookAhead { 328 | return "syntax error: " + e.msg 329 | } 330 | } 331 | 332 | res := "syntax error: unexpected " + yyTokname(lookAhead) 333 | 334 | // To match Bison, suggest at most four expected tokens. 335 | expected := make([]int, 0, 4) 336 | 337 | // Look for shiftable tokens. 338 | base := int(yyPact[state]) 339 | for tok := TOKSTART; tok-1 < len(yyToknames); tok++ { 340 | if n := base + tok; n >= 0 && n < yyLast && int(yyChk[int(yyAct[n])]) == tok { 341 | if len(expected) == cap(expected) { 342 | return res 343 | } 344 | expected = append(expected, tok) 345 | } 346 | } 347 | 348 | if yyDef[state] == -2 { 349 | i := 0 350 | for yyExca[i] != -1 || int(yyExca[i+1]) != state { 351 | i += 2 352 | } 353 | 354 | // Look for tokens that we accept or reduce. 355 | for i += 2; yyExca[i] >= 0; i += 2 { 356 | tok := int(yyExca[i]) 357 | if tok < TOKSTART || yyExca[i+1] == 0 { 358 | continue 359 | } 360 | if len(expected) == cap(expected) { 361 | return res 362 | } 363 | expected = append(expected, tok) 364 | } 365 | 366 | // If the default action is to accept or reduce, give up. 367 | if yyExca[i+1] != 0 { 368 | return res 369 | } 370 | } 371 | 372 | for i, tok := range expected { 373 | if i == 0 { 374 | res += ", expecting " 375 | } else { 376 | res += " or " 377 | } 378 | res += yyTokname(tok) 379 | } 380 | return res 381 | } 382 | 383 | func yylex1(lex yyLexer, lval *yySymType) (char, token int) { 384 | token = 0 385 | char = lex.Lex(lval) 386 | if char <= 0 { 387 | token = int(yyTok1[0]) 388 | goto out 389 | } 390 | if char < len(yyTok1) { 391 | token = int(yyTok1[char]) 392 | goto out 393 | } 394 | if char >= yyPrivate { 395 | if char < yyPrivate+len(yyTok2) { 396 | token = int(yyTok2[char-yyPrivate]) 397 | goto out 398 | } 399 | } 400 | for i := 0; i < len(yyTok3); i += 2 { 401 | token = int(yyTok3[i+0]) 402 | if token == char { 403 | token = int(yyTok3[i+1]) 404 | goto out 405 | } 406 | } 407 | 408 | out: 409 | if token == 0 { 410 | token = int(yyTok2[1]) /* unknown char */ 411 | } 412 | if yyDebug >= 3 { 413 | __yyfmt__.Printf("lex %s(%d)\n", yyTokname(token), uint(char)) 414 | } 415 | return char, token 416 | } 417 | 418 | func yyParse(yylex yyLexer) int { 419 | return yyNewParser().Parse(yylex) 420 | } 421 | 422 | func (yyrcvr *yyParserImpl) Parse(yylex yyLexer) int { 423 | var yyn int 424 | var yyVAL yySymType 425 | var yyDollar []yySymType 426 | _ = yyDollar // silence set and not used 427 | yyS := yyrcvr.stack[:] 428 | 429 | Nerrs := 0 /* number of errors */ 430 | Errflag := 0 /* error recovery flag */ 431 | yystate := 0 432 | yyrcvr.char = -1 433 | yytoken := -1 // yyrcvr.char translated into internal numbering 434 | defer func() { 435 | // Make sure we report no lookahead when not parsing. 436 | yystate = -1 437 | yyrcvr.char = -1 438 | yytoken = -1 439 | }() 440 | yyp := -1 441 | goto yystack 442 | 443 | ret0: 444 | return 0 445 | 446 | ret1: 447 | return 1 448 | 449 | yystack: 450 | /* put a state and value onto the stack */ 451 | if yyDebug >= 4 { 452 | __yyfmt__.Printf("char %v in %v\n", yyTokname(yytoken), yyStatname(yystate)) 453 | } 454 | 455 | yyp++ 456 | if yyp >= len(yyS) { 457 | nyys := make([]yySymType, len(yyS)*2) 458 | copy(nyys, yyS) 459 | yyS = nyys 460 | } 461 | yyS[yyp] = yyVAL 462 | yyS[yyp].yys = yystate 463 | 464 | yynewstate: 465 | yyn = int(yyPact[yystate]) 466 | if yyn <= yyFlag { 467 | goto yydefault /* simple state */ 468 | } 469 | if yyrcvr.char < 0 { 470 | yyrcvr.char, yytoken = yylex1(yylex, &yyrcvr.lval) 471 | } 472 | yyn += yytoken 473 | if yyn < 0 || yyn >= yyLast { 474 | goto yydefault 475 | } 476 | yyn = int(yyAct[yyn]) 477 | if int(yyChk[yyn]) == yytoken { /* valid shift */ 478 | yyrcvr.char = -1 479 | yytoken = -1 480 | yyVAL = yyrcvr.lval 481 | yystate = yyn 482 | if Errflag > 0 { 483 | Errflag-- 484 | } 485 | goto yystack 486 | } 487 | 488 | yydefault: 489 | /* default state action */ 490 | yyn = int(yyDef[yystate]) 491 | if yyn == -2 { 492 | if yyrcvr.char < 0 { 493 | yyrcvr.char, yytoken = yylex1(yylex, &yyrcvr.lval) 494 | } 495 | 496 | /* look through exception table */ 497 | xi := 0 498 | for { 499 | if yyExca[xi+0] == -1 && int(yyExca[xi+1]) == yystate { 500 | break 501 | } 502 | xi += 2 503 | } 504 | for xi += 2; ; xi += 2 { 505 | yyn = int(yyExca[xi+0]) 506 | if yyn < 0 || yyn == yytoken { 507 | break 508 | } 509 | } 510 | yyn = int(yyExca[xi+1]) 511 | if yyn < 0 { 512 | goto ret0 513 | } 514 | } 515 | if yyn == 0 { 516 | /* error ... attempt to resume parsing */ 517 | switch Errflag { 518 | case 0: /* brand new error */ 519 | yylex.Error(yyErrorMessage(yystate, yytoken)) 520 | Nerrs++ 521 | if yyDebug >= 1 { 522 | __yyfmt__.Printf("%s", yyStatname(yystate)) 523 | __yyfmt__.Printf(" saw %s\n", yyTokname(yytoken)) 524 | } 525 | fallthrough 526 | 527 | case 1, 2: /* incompletely recovered error ... try again */ 528 | Errflag = 3 529 | 530 | /* find a state where "error" is a legal shift action */ 531 | for yyp >= 0 { 532 | yyn = int(yyPact[yyS[yyp].yys]) + yyErrCode 533 | if yyn >= 0 && yyn < yyLast { 534 | yystate = int(yyAct[yyn]) /* simulate a shift of "error" */ 535 | if int(yyChk[yystate]) == yyErrCode { 536 | goto yystack 537 | } 538 | } 539 | 540 | /* the current p has no shift on "error", pop stack */ 541 | if yyDebug >= 2 { 542 | __yyfmt__.Printf("error recovery pops state %d\n", yyS[yyp].yys) 543 | } 544 | yyp-- 545 | } 546 | /* there is no state on the stack with an error shift ... abort */ 547 | goto ret1 548 | 549 | case 3: /* no shift yet; clobber input char */ 550 | if yyDebug >= 2 { 551 | __yyfmt__.Printf("error recovery discards %s\n", yyTokname(yytoken)) 552 | } 553 | if yytoken == yyEofCode { 554 | goto ret1 555 | } 556 | yyrcvr.char = -1 557 | yytoken = -1 558 | goto yynewstate /* try again in the same state */ 559 | } 560 | } 561 | 562 | /* reduction by production yyn */ 563 | if yyDebug >= 2 { 564 | __yyfmt__.Printf("reduce %v in:\n\t%v\n", yyn, yyStatname(yystate)) 565 | } 566 | 567 | yynt := yyn 568 | yypt := yyp 569 | _ = yypt // guard against "declared and not used" 570 | 571 | yyp -= int(yyR2[yyn]) 572 | // yyp is now the index of $0. Perform the default action. Iff the 573 | // reduced production is ε, $1 is possibly out of range. 574 | if yyp+1 >= len(yyS) { 575 | nyys := make([]yySymType, len(yyS)*2) 576 | copy(nyys, yyS) 577 | yyS = nyys 578 | } 579 | yyVAL = yyS[yyp+1] 580 | 581 | /* consult goto table to find next state */ 582 | yyn = int(yyR1[yyn]) 583 | yyg := int(yyPgo[yyn]) 584 | yyj := yyg + yyS[yyp].yys + 1 585 | 586 | if yyj >= yyLast { 587 | yystate = int(yyAct[yyg]) 588 | } else { 589 | yystate = int(yyAct[yyj]) 590 | if int(yyChk[yystate]) != -yyn { 591 | yystate = int(yyAct[yyg]) 592 | } 593 | } 594 | // dummy call; replaced with literal code 595 | switch yynt { 596 | 597 | case 12: 598 | yyDollar = yyS[yypt-5 : yypt+1] 599 | //line sunrpc.y:88 600 | { 601 | StartEnum(yyDollar[2].val) 602 | } 603 | case 15: 604 | yyDollar = yyS[yypt-1 : yypt+1] 605 | //line sunrpc.y:97 606 | { 607 | err := AddEnumAutoVal(yyDollar[1].val) 608 | if err != nil { 609 | yylex.Error(err.Error()) 610 | return 1 611 | } 612 | } 613 | case 16: 614 | yyDollar = yyS[yypt-3 : yypt+1] 615 | //line sunrpc.y:104 616 | { 617 | err := AddEnumVal(yyDollar[1].val, yyDollar[3].val) 618 | if err != nil { 619 | yylex.Error(err.Error()) 620 | return 1 621 | } 622 | } 623 | case 17: 624 | yyDollar = yyS[yypt-3 : yypt+1] 625 | //line sunrpc.y:111 626 | { 627 | err := AddProcEnumVal(yyDollar[1].val, yyDollar[3].val, "") 628 | if err != nil { 629 | yylex.Error(err.Error()) 630 | return 1 631 | } 632 | } 633 | case 18: 634 | yyDollar = yyS[yypt-4 : yypt+1] 635 | //line sunrpc.y:118 636 | { 637 | err := AddProcEnumVal(yyDollar[2].val, yyDollar[4].val, yyDollar[1].val) 638 | if err != nil { 639 | yylex.Error(err.Error()) 640 | return 1 641 | } 642 | } 643 | case 23: 644 | yyDollar = yyS[yypt-4 : yypt+1] 645 | //line sunrpc.y:144 646 | { 647 | err := AddConst(yyDollar[2].val, yyDollar[4].val) 648 | if err != nil { 649 | yylex.Error(err.Error()) 650 | return 1 651 | } 652 | } 653 | case 25: 654 | yyDollar = yyS[yypt-1 : yypt+1] 655 | //line sunrpc.y:158 656 | { 657 | StartTypedef() 658 | } 659 | case 31: 660 | yyDollar = yyS[yypt-2 : yypt+1] 661 | //line sunrpc.y:169 662 | { 663 | AddDeclaration(yyDollar[2].val, yyDollar[1].val) 664 | } 665 | case 33: 666 | yyDollar = yyS[yypt-2 : yypt+1] 667 | //line sunrpc.y:174 668 | { 669 | yyVAL.val = "u" + yyDollar[2].val 670 | } 671 | case 34: 672 | yyDollar = yyS[yypt-1 : yypt+1] 673 | //line sunrpc.y:175 674 | { 675 | yyVAL.val = "float32" 676 | } 677 | case 35: 678 | yyDollar = yyS[yypt-1 : yypt+1] 679 | //line sunrpc.y:176 680 | { 681 | yyVAL.val = "float64" 682 | } 683 | case 36: 684 | yyDollar = yyS[yypt-1 : yypt+1] 685 | //line sunrpc.y:177 686 | { 687 | yyVAL.val = "bool" 688 | } 689 | case 37: 690 | yyDollar = yyS[yypt-1 : yypt+1] 691 | //line sunrpc.y:178 692 | { 693 | yyVAL.val = "string" 694 | } 695 | case 38: 696 | yyDollar = yyS[yypt-1 : yypt+1] 697 | //line sunrpc.y:179 698 | { 699 | yyVAL.val = "byte" 700 | } 701 | case 43: 702 | yyDollar = yyS[yypt-1 : yypt+1] 703 | //line sunrpc.y:187 704 | { 705 | yyVAL.val = "int64" 706 | } 707 | case 44: 708 | yyDollar = yyS[yypt-1 : yypt+1] 709 | //line sunrpc.y:188 710 | { 711 | yyVAL.val = "int32" 712 | } 713 | case 45: 714 | yyDollar = yyS[yypt-1 : yypt+1] 715 | //line sunrpc.y:189 716 | { 717 | yyVAL.val = "int16" 718 | } 719 | case 46: 720 | yyDollar = yyS[yypt-1 : yypt+1] 721 | //line sunrpc.y:190 722 | { 723 | yyVAL.val = "int8" 724 | } 725 | case 48: 726 | yyDollar = yyS[yypt-5 : yypt+1] 727 | //line sunrpc.y:198 728 | { 729 | AddFixedArray(yyDollar[2].val, yyDollar[1].val, yyDollar[4].val) 730 | } 731 | case 49: 732 | yyDollar = yyS[yypt-5 : yypt+1] 733 | //line sunrpc.y:202 734 | { 735 | AddVariableArray(yyDollar[2].val, yyDollar[1].val, yyDollar[4].val) 736 | } 737 | case 50: 738 | yyDollar = yyS[yypt-4 : yypt+1] 739 | //line sunrpc.y:203 740 | { 741 | AddVariableArray(yyDollar[2].val, yyDollar[1].val, "") 742 | } 743 | case 51: 744 | yyDollar = yyS[yypt-3 : yypt+1] 745 | //line sunrpc.y:211 746 | { 747 | AddOptValue(yyDollar[3].val, yyDollar[1].val) 748 | } 749 | case 52: 750 | yyDollar = yyS[yypt-3 : yypt+1] 751 | //line sunrpc.y:215 752 | { 753 | StartStruct(yyDollar[2].val) 754 | } 755 | case 53: 756 | yyDollar = yyS[yypt-6 : yypt+1] 757 | //line sunrpc.y:215 758 | { 759 | AddStruct() 760 | } 761 | case 57: 762 | yyDollar = yyS[yypt-2 : yypt+1] 763 | //line sunrpc.y:228 764 | { 765 | StartUnion(yyDollar[2].val) 766 | } 767 | case 58: 768 | yyDollar = yyS[yypt-10 : yypt+1] 769 | //line sunrpc.y:228 770 | { 771 | AddUnion() 772 | } 773 | case 62: 774 | yyDollar = yyS[yypt-2 : yypt+1] 775 | //line sunrpc.y:241 776 | { 777 | StartCase(yyDollar[2].val) 778 | } 779 | case 63: 780 | yyDollar = yyS[yypt-5 : yypt+1] 781 | //line sunrpc.y:241 782 | { 783 | AddCase() 784 | } 785 | case 64: 786 | yyDollar = yyS[yypt-1 : yypt+1] 787 | //line sunrpc.y:242 788 | { 789 | StartCase("default") 790 | } 791 | case 65: 792 | yyDollar = yyS[yypt-4 : yypt+1] 793 | //line sunrpc.y:242 794 | { 795 | AddCase() 796 | } 797 | } 798 | goto yystack /* stack new state and value */ 799 | } 800 | -------------------------------------------------------------------------------- /libvirt.yml: -------------------------------------------------------------------------------- 1 | # Configuration file for c-for-go, which go-libvirt uses to translate the const 2 | # and type definitions from the C-language sources in the libvirt project into 3 | # Go. This file is used by the c-for-go binary (github.com/xlab/c-for-go), which 4 | # is called when 'go generate' is run. See libvirt.go for the command line used. 5 | --- 6 | GENERATOR: 7 | PackageName: libvirt 8 | PackageLicense: | 9 | Copyright 2018 The go-libvirt Authors. 10 | 11 | Licensed under the Apache License, Version 2.0 (the "License"); 12 | you may not use this file except in compliance with the License. 13 | You may obtain a copy of the License at 14 | 15 | http://www.apache.org/licenses/LICENSE-2.0 16 | 17 | Unless required by applicable law or agreed to in writing, software 18 | distributed under the License is distributed on an "AS IS" BASIS, 19 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | See the License for the specific language governing permissions and 21 | limitations under the License. 22 | Includes: [] 23 | 24 | PARSER: 25 | # We can't use environment variables here, but we don't want to process the 26 | # libvirt version installed in the system folders (if any). Instead we'll 27 | # rely on our caller to link the libvirt source directory to lv_source/, and 28 | # run on that code. This isn't ideal, but changes to c-for-go are needed to 29 | # fix it. 30 | IncludePaths: [./lv_source/include, ./lv_source/build/include] 31 | SysIncludePaths: [./lv_source/include, ./lv_source/build/include] 32 | SourcesPaths: 33 | - libvirt/libvirt.h 34 | - libvirt/virterror.h 35 | 36 | TRANSLATOR: 37 | ConstRules: 38 | defines: eval 39 | TypeTips: 40 | enum: 41 | - { target: "ConnectGetAllDomainStatsFlags", tips: [ unsigned ] } 42 | - { target: "ConnectListAllNodeDeviceFlags", tips: [ unsigned ] } 43 | Rules: 44 | global: 45 | - {action: accept, from: "^vir"} 46 | post-global: 47 | - {action: replace, from: "^vir"} 48 | - {load: snakecase} 49 | # Follow golint's capitalization conventions. 50 | - {action: replace, from: "Api([A-Z]|$)", to: "API$1"} 51 | - {action: replace, from: "Cpu([A-Z]|$)", to: "CPU$1"} 52 | - {action: replace, from: "Dns([A-Z]|$)", to: "DNS$1"} 53 | - {action: replace, from: "Eof([A-Z]|$)", to: "EOF$1"} 54 | - {action: replace, from: "Id([A-Z]|$)", to: "ID$1"} 55 | - {action: replace, from: "Ip([A-Z]|$)", to: "IP$1"} 56 | - {action: replace, from: "Tls([A-Z]|$)", to: "TLS$1"} 57 | - {action: replace, from: "Uuid([A-Z]|$)", to: "UUID$1"} 58 | - {action: replace, from: "Uri([A-Z]|$)", to: "URI$1"} 59 | - {action: replace, from: "Vcpu([A-Z]|$)", to: "VCPU$1"} 60 | - {action: replace, from: "Xml([A-Z]|$)", to: "XML$1"} 61 | - {action: replace, from: "Rpc([A-Z]|$)", to: "RPC$1"} 62 | - {action: replace, from: "Ssh([A-Z]|$)", to: "SSH$1"} 63 | - {action: replace, from: "Http([A-Z]|$)", to: "HTTP$1"} 64 | - {transform: unexport, from: "^From"} 65 | const: 66 | - {action: accept, from: "^VIR_"} 67 | # Special case to prevent a collision with a type: 68 | - {action: replace, from: "^VIR_DOMAIN_JOB_OPERATION", to: "VIR_DOMAIN_JOB_OPERATION_STR"} 69 | - {transform: lower} 70 | -------------------------------------------------------------------------------- /libvirt_integration_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The go-libvirt Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // +build integration 16 | 17 | package libvirt 18 | 19 | import ( 20 | "bytes" 21 | "encoding/xml" 22 | "errors" 23 | "net" 24 | "sync" 25 | "testing" 26 | "time" 27 | 28 | "github.com/digitalocean/go-libvirt/socket" 29 | "github.com/digitalocean/go-libvirt/socket/dialers" 30 | ) 31 | 32 | // In order for this test to work, libvirtd must be running and listening for 33 | // tcp connections. Then the items (domain, secret, storage pool) in the top level testdata directory 34 | // need to have been previously defined. (See the "Setup test artifacts" steps 35 | // in .github/workflows/main.yml for how to use virsh for this.) 36 | // 37 | // TODO: The setup steps should be moved into a script that can be manually 38 | // called or used by ci. 39 | 40 | const ( 41 | testAddr = "127.0.0.1" 42 | testPort = "16509" 43 | 44 | testDomainName = "test" 45 | ) 46 | 47 | func TestDeprecatedConnectDisconnectIntegration(t *testing.T) { 48 | l := New(testConn(t)) 49 | 50 | if err := l.Connect(); err != nil { 51 | t.Fatal(err) 52 | } 53 | 54 | if err := l.Disconnect(); err != nil { 55 | t.Fatal(err) 56 | } 57 | } 58 | 59 | func TestConnectDisconnectIntegration(t *testing.T) { 60 | l := NewWithDialer(testDialer(t)) 61 | 62 | if err := l.Connect(); err != nil { 63 | t.Fatal(err) 64 | } 65 | 66 | if err := l.Disconnect(); err != nil { 67 | t.Fatal(err) 68 | } 69 | } 70 | 71 | func TestConnectToURIIntegration(t *testing.T) { 72 | l := NewWithDialer(testDialer(t)) 73 | 74 | if err := l.ConnectToURI(TestDefault); err != nil { 75 | t.Error(err) 76 | } 77 | defer l.Disconnect() 78 | } 79 | 80 | func checkCapabilities(t *testing.T, l *Libvirt) error { 81 | t.Helper() 82 | 83 | resp, err := l.Capabilities() 84 | if err != nil { 85 | return err 86 | } 87 | 88 | // verify UUID exists within returned XML 89 | var caps struct { 90 | Host struct { 91 | UUID string `xml:"uuid"` 92 | } `xml:"host"` 93 | } 94 | 95 | if err := xml.Unmarshal(resp, &caps); err != nil { 96 | return err 97 | } 98 | 99 | if caps.Host.UUID == "" { 100 | return errors.New("expected capabilities to contain a UUID") 101 | } 102 | 103 | return nil 104 | } 105 | 106 | func TestDeprecatedCapabilities(t *testing.T) { 107 | l := New(testConn(t)) 108 | 109 | if err := l.Connect(); err != nil { 110 | t.Fatal(err) 111 | } 112 | defer l.Disconnect() 113 | 114 | if err := checkCapabilities(t, l); err != nil { 115 | t.Errorf("check capabilities error: %v", err) 116 | } 117 | } 118 | 119 | func TestCapabilities(t *testing.T) { 120 | l := NewWithDialer(testDialer(t)) 121 | 122 | if err := l.Connect(); err != nil { 123 | t.Fatal(err) 124 | } 125 | defer l.Disconnect() 126 | 127 | if err := checkCapabilities(t, l); err != nil { 128 | t.Errorf("check capabilities error: %v", err) 129 | } 130 | } 131 | 132 | func TestUsingNeverConnected(t *testing.T) { 133 | l := NewWithDialer(testDialer(t)) 134 | 135 | // should fail because Connect was never called 136 | _, err := l.DomainLookupByName(testDomainName) 137 | if err == nil { 138 | t.Fatal("using a never connected libvirt should fail") 139 | } 140 | } 141 | 142 | func TestUsingAfterDisconnect(t *testing.T) { 143 | l := NewWithDialer(testDialer(t)) 144 | 145 | if err := l.Connect(); err != nil { 146 | t.Fatal(err) 147 | } 148 | 149 | if err := l.Disconnect(); err != nil { 150 | t.Fatal(err) 151 | } 152 | 153 | // should fail because Disconnect was already called 154 | _, err := l.DomainLookupByName(testDomainName) 155 | if err == nil { 156 | t.Fatal("using a disconnected libvirt should fail") 157 | } 158 | } 159 | 160 | func TestLostConnection(t *testing.T) { 161 | // In order to be able to close the connection external to libvirt, 162 | // we use an already established dialer. 163 | conn := testConn(t) 164 | l := NewWithDialer(dialers.NewAlreadyConnected(conn)) 165 | 166 | if err := l.Connect(); err != nil { 167 | t.Fatal(err) 168 | } 169 | 170 | // forcibly close the connection out from under the Libvirt object 171 | conn.Close() 172 | 173 | <-l.Disconnected() 174 | 175 | // this should return an error about the connection being lost 176 | _, err := l.DomainLookupByName(testDomainName) 177 | if err == nil { 178 | t.Error("using a libvirt with a lost connection should fail") 179 | } 180 | 181 | // Disconnect should still return success when the connection was already 182 | // lost. 183 | if err := l.Disconnect(); err != nil { 184 | t.Fatalf("disconnect should still succeed after connection is lost"+ 185 | ": %v", err) 186 | } 187 | 188 | } 189 | 190 | func TestMultipleConnections(t *testing.T) { 191 | l := NewWithDialer(testDialer(t)) 192 | 193 | if err := l.Connect(); err != nil { 194 | t.Fatal(err) 195 | } 196 | if err := l.Disconnect(); err != nil { 197 | t.Fatal(err) 198 | } 199 | 200 | // Now connect again and make sure stuff still works 201 | if err := l.Connect(); err != nil { 202 | t.Fatal(err) 203 | } 204 | defer l.Disconnect() 205 | 206 | if err := checkCapabilities(t, l); err != nil { 207 | t.Errorf("failed to use reconnected libvirt: %v", err) 208 | } 209 | } 210 | 211 | func TestSecretsIntegration(t *testing.T) { 212 | l := NewWithDialer(testDialer(t)) 213 | 214 | if err := l.Connect(); err != nil { 215 | t.Fatal(err) 216 | } 217 | defer l.Disconnect() 218 | 219 | secrets, err := l.Secrets() 220 | if err != nil { 221 | t.Fatal(err) 222 | } 223 | 224 | wantLen := 1 225 | gotLen := len(secrets) 226 | if gotLen != wantLen { 227 | t.Fatalf("expected %d secrets, got %d", wantLen, gotLen) 228 | } 229 | 230 | s := secrets[0] 231 | 232 | wantType := SecretUsageTypeVolume 233 | if s.UsageType != int32(wantType) { 234 | t.Errorf("expected usage type: %d, got %d", wantType, s.UsageType) 235 | } 236 | 237 | wantID := "/tmp" 238 | if s.UsageID != wantID { 239 | t.Errorf("expected usage id: %q, got %q", wantID, s.UsageID) 240 | } 241 | 242 | // 19fdc2f2-fa64-46f3-bacf-42a8aafca6dd 243 | wantUUID := [UUIDBuflen]byte{ 244 | 0x19, 0xfd, 0xc2, 0xf2, 0xfa, 0x64, 0x46, 0xf3, 245 | 0xba, 0xcf, 0x42, 0xa8, 0xaa, 0xfc, 0xa6, 0xdd, 246 | } 247 | if s.UUID != wantUUID { 248 | t.Errorf("expected UUID %q, got %q", wantUUID, s.UUID) 249 | } 250 | } 251 | 252 | func TestStoragePoolIntegration(t *testing.T) { 253 | l := NewWithDialer(testDialer(t)) 254 | 255 | if err := l.Connect(); err != nil { 256 | t.Fatal(err) 257 | } 258 | defer l.Disconnect() 259 | 260 | wantName := "test" 261 | pool, err := l.StoragePool(wantName) 262 | if err != nil { 263 | t.Fatal(err) 264 | } 265 | 266 | gotName := pool.Name 267 | if gotName != wantName { 268 | t.Errorf("expected name %q, got %q", wantName, gotName) 269 | } 270 | } 271 | 272 | func TestStoragePoolInvalidIntegration(t *testing.T) { 273 | l := NewWithDialer(testDialer(t)) 274 | 275 | if err := l.Connect(); err != nil { 276 | t.Fatal(err) 277 | } 278 | defer l.Disconnect() 279 | 280 | _, err := l.StoragePool("test-does-not-exist") 281 | if err == nil { 282 | t.Errorf("expected non-existent storage pool return error") 283 | } 284 | } 285 | 286 | func TestStoragePoolsIntegration(t *testing.T) { 287 | l := NewWithDialer(testDialer(t)) 288 | 289 | if err := l.Connect(); err != nil { 290 | t.Fatal(err) 291 | } 292 | defer l.Disconnect() 293 | 294 | pools, err := l.StoragePools(ConnectListStoragePoolsActive) 295 | if err != nil { 296 | t.Error(err) 297 | } 298 | 299 | wantLen := 1 300 | gotLen := len(pools) 301 | if gotLen != wantLen { 302 | t.Fatalf("expected %d storage pool, got %d", wantLen, gotLen) 303 | } 304 | 305 | wantName := "test" 306 | gotName := pools[0].Name 307 | if gotName != wantName { 308 | t.Errorf("expected name %q, got %q", wantName, gotName) 309 | } 310 | } 311 | 312 | func TestStoragePoolsAutostartIntegration(t *testing.T) { 313 | l := NewWithDialer(testDialer(t)) 314 | 315 | if err := l.Connect(); err != nil { 316 | t.Fatal(err) 317 | } 318 | defer l.Disconnect() 319 | 320 | pools, err := l.StoragePools(ConnectListStoragePoolsAutostart) 321 | if err != nil { 322 | t.Error(err) 323 | } 324 | 325 | wantLen := 0 326 | gotLen := len(pools) 327 | if gotLen != wantLen { 328 | t.Errorf("expected %d storage pool, got %d", wantLen, gotLen) 329 | } 330 | } 331 | 332 | func TestStoragePoolRefreshIntegration(t *testing.T) { 333 | l := NewWithDialer(testDialer(t)) 334 | 335 | if err := l.Connect(); err != nil { 336 | t.Fatal(err) 337 | } 338 | defer l.Disconnect() 339 | 340 | pool, err := l.StoragePool("test") 341 | if err != nil { 342 | t.Error(err) 343 | } 344 | 345 | err = l.StoragePoolRefresh(pool, 0) 346 | if err != nil { 347 | t.Error(err) 348 | } 349 | } 350 | 351 | func TestStoragePoolRefreshInvalidIntegration(t *testing.T) { 352 | l := NewWithDialer(testDialer(t)) 353 | 354 | if err := l.Connect(); err != nil { 355 | t.Fatal(err) 356 | } 357 | defer l.Disconnect() 358 | 359 | pool, err := l.StoragePool("test-does-not-exist") 360 | if err == nil { 361 | t.Error(err) 362 | } 363 | 364 | err = l.StoragePoolRefresh(pool, 0) 365 | if err == nil { 366 | t.Error("expected non-existent storage pool to fail refresh") 367 | } 368 | } 369 | 370 | func TestXMLIntegration(t *testing.T) { 371 | l := NewWithDialer(testDialer(t)) 372 | 373 | if err := l.Connect(); err != nil { 374 | t.Error(err) 375 | } 376 | defer l.Disconnect() 377 | 378 | var flags DomainXMLFlags 379 | data, err := l.XML("test", flags) 380 | if err != nil { 381 | t.Fatal(err) 382 | } 383 | 384 | var v interface{} 385 | if err := xml.Unmarshal(data, &v); err != nil { 386 | t.Error(err) 387 | } 388 | } 389 | 390 | func TestVolumeUploadDownloadIntegration(t *testing.T) { 391 | testdata := []byte("Hello, world!") 392 | l := NewWithDialer(testDialer(t)) 393 | 394 | if err := l.Connect(); err != nil { 395 | t.Error(err) 396 | } 397 | defer l.Disconnect() 398 | 399 | pool, err := l.StoragePool("test") 400 | if err != nil { 401 | t.Fatal(err) 402 | } 403 | 404 | var volObj struct { 405 | XMLName xml.Name `xml:"volume"` 406 | Name string `xml:"name"` 407 | Capacity struct { 408 | Value uint64 `xml:",chardata"` 409 | } `xml:"capacity"` 410 | Target struct { 411 | Format struct { 412 | Type string `xml:"type,attr"` 413 | } `xml:"format"` 414 | } `xml:"target"` 415 | } 416 | volObj.Name = "testvol" 417 | volObj.Capacity.Value = uint64(len(testdata)) 418 | volObj.Target.Format.Type = "raw" 419 | xmlVol, err := xml.Marshal(volObj) 420 | if err != nil { 421 | t.Fatal(err) 422 | } 423 | vol, err := l.StorageVolCreateXML(pool, string(xmlVol), 0) 424 | if err != nil { 425 | t.Fatal(err) 426 | } 427 | defer l.StorageVolDelete(vol, 0) 428 | err = l.StorageVolUpload(vol, bytes.NewBuffer(testdata), 0, 0, 0) 429 | if err != nil { 430 | t.Fatal(err) 431 | } 432 | var buf bytes.Buffer 433 | err = l.StorageVolDownload(vol, &buf, 0, 0, 0) 434 | if err != nil { 435 | t.Fatal(err) 436 | } 437 | if bytes.Compare(testdata, buf.Bytes()) != 0 { 438 | t.Fatal("download not what we uploaded") 439 | } 440 | } 441 | 442 | // verify we're able to concurrently communicate with libvirtd. 443 | // see: https://github.com/digitalocean/go-libvirt/pull/52 444 | func Test_concurrentWrite(t *testing.T) { 445 | l := NewWithDialer(testDialer(t)) 446 | 447 | if err := l.Connect(); err != nil { 448 | t.Error(err) 449 | } 450 | defer l.Disconnect() 451 | 452 | count := 10 453 | wg := sync.WaitGroup{} 454 | wg.Add(count) 455 | 456 | start := make(chan struct{}) 457 | done := make(chan struct{}) 458 | 459 | go func() { 460 | wg.Wait() 461 | close(done) 462 | }() 463 | 464 | for i := 0; i < count; i++ { 465 | go func() { 466 | defer wg.Done() 467 | <-start 468 | 469 | _, err := l.Domains() 470 | if err != nil { 471 | t.Fatal(err) 472 | } 473 | }() 474 | } 475 | 476 | close(start) 477 | 478 | select { 479 | case <-done: 480 | case <-time.After(10 * time.Second): 481 | t.Fatal("timed out waiting for execution to complete") 482 | } 483 | } 484 | 485 | func testDialer(t *testing.T) socket.Dialer { 486 | t.Helper() 487 | 488 | return dialers.NewRemote( 489 | testAddr, 490 | dialers.UsePort(testPort), 491 | dialers.WithRemoteTimeout(time.Second*2), 492 | ) 493 | } 494 | 495 | func testConn(t *testing.T) net.Conn { 496 | t.Helper() 497 | 498 | dialer := testDialer(t) 499 | conn, err := dialer.Dial() 500 | if err != nil { 501 | t.Fatal(err) 502 | } 503 | 504 | return conn 505 | } 506 | -------------------------------------------------------------------------------- /qemu_protocol.gen.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The go-libvirt Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // 16 | // Code generated by internal/lvgen/generate.go. DO NOT EDIT. 17 | // 18 | // To regenerate, run 'go generate' in internal/lvgen. 19 | // 20 | 21 | package libvirt 22 | 23 | import ( 24 | "bytes" 25 | "io" 26 | 27 | "github.com/digitalocean/go-libvirt/internal/constants" 28 | "github.com/digitalocean/go-libvirt/internal/go-xdr/xdr2" 29 | ) 30 | 31 | // References to prevent "imported and not used" errors. 32 | var ( 33 | _ = bytes.Buffer{} 34 | _ = io.Copy 35 | _ = constants.Program 36 | _ = xdr.Unmarshal 37 | ) 38 | 39 | // 40 | // Typedefs: 41 | // 42 | 43 | // 44 | // Enums: 45 | // 46 | // QEMUProcedure is libvirt's qemu_procedure 47 | type QEMUProcedure int32 48 | 49 | // 50 | // Structs: 51 | // 52 | // QEMUDomainMonitorCommandArgs is libvirt's qemu_domain_monitor_command_args 53 | type QEMUDomainMonitorCommandArgs struct { 54 | Dom Domain 55 | Cmd string 56 | Flags uint32 57 | } 58 | 59 | // QEMUDomainMonitorCommandRet is libvirt's qemu_domain_monitor_command_ret 60 | type QEMUDomainMonitorCommandRet struct { 61 | Result string 62 | } 63 | 64 | // QEMUDomainAttachArgs is libvirt's qemu_domain_attach_args 65 | type QEMUDomainAttachArgs struct { 66 | PidValue uint32 67 | Flags uint32 68 | } 69 | 70 | // QEMUDomainAttachRet is libvirt's qemu_domain_attach_ret 71 | type QEMUDomainAttachRet struct { 72 | Dom Domain 73 | } 74 | 75 | // QEMUDomainAgentCommandArgs is libvirt's qemu_domain_agent_command_args 76 | type QEMUDomainAgentCommandArgs struct { 77 | Dom Domain 78 | Cmd string 79 | Timeout int32 80 | Flags uint32 81 | } 82 | 83 | // QEMUDomainAgentCommandRet is libvirt's qemu_domain_agent_command_ret 84 | type QEMUDomainAgentCommandRet struct { 85 | Result OptString 86 | } 87 | 88 | // QEMUConnectDomainMonitorEventRegisterArgs is libvirt's qemu_connect_domain_monitor_event_register_args 89 | type QEMUConnectDomainMonitorEventRegisterArgs struct { 90 | Dom OptDomain 91 | Event OptString 92 | Flags uint32 93 | } 94 | 95 | // QEMUConnectDomainMonitorEventRegisterRet is libvirt's qemu_connect_domain_monitor_event_register_ret 96 | type QEMUConnectDomainMonitorEventRegisterRet struct { 97 | CallbackID int32 98 | } 99 | 100 | // QEMUConnectDomainMonitorEventDeregisterArgs is libvirt's qemu_connect_domain_monitor_event_deregister_args 101 | type QEMUConnectDomainMonitorEventDeregisterArgs struct { 102 | CallbackID int32 103 | } 104 | 105 | // QEMUDomainMonitorEventMsg is libvirt's qemu_domain_monitor_event_msg 106 | type QEMUDomainMonitorEventMsg struct { 107 | CallbackID int32 108 | Dom Domain 109 | Event string 110 | Seconds int64 111 | Micros uint32 112 | Details OptString 113 | } 114 | 115 | 116 | 117 | 118 | // QEMUDomainMonitorCommand is the go wrapper for QEMU_PROC_DOMAIN_MONITOR_COMMAND. 119 | func (l *Libvirt) QEMUDomainMonitorCommand(Dom Domain, Cmd string, Flags uint32) (rResult string, err error) { 120 | var buf []byte 121 | 122 | args := QEMUDomainMonitorCommandArgs { 123 | Dom: Dom, 124 | Cmd: Cmd, 125 | Flags: Flags, 126 | } 127 | 128 | buf, err = encode(&args) 129 | if err != nil { 130 | return 131 | } 132 | 133 | var r response 134 | r, err = l.requestStream(1, constants.QEMUProgram, buf, nil, nil) 135 | if err != nil { 136 | return 137 | } 138 | 139 | // Return value unmarshaling 140 | tpd := typedParamDecoder{} 141 | ct := map[string]xdr.TypeDecoder{"libvirt.TypedParam": tpd} 142 | rdr := bytes.NewReader(r.Payload) 143 | dec := xdr.NewDecoderCustomTypes(rdr, 0, ct) 144 | // Result: string 145 | _, err = dec.Decode(&rResult) 146 | if err != nil { 147 | return 148 | } 149 | 150 | return 151 | } 152 | 153 | // QEMUDomainAttach is the go wrapper for QEMU_PROC_DOMAIN_ATTACH. 154 | func (l *Libvirt) QEMUDomainAttach(PidValue uint32, Flags uint32) (rDom Domain, err error) { 155 | var buf []byte 156 | 157 | args := QEMUDomainAttachArgs { 158 | PidValue: PidValue, 159 | Flags: Flags, 160 | } 161 | 162 | buf, err = encode(&args) 163 | if err != nil { 164 | return 165 | } 166 | 167 | var r response 168 | r, err = l.requestStream(2, constants.QEMUProgram, buf, nil, nil) 169 | if err != nil { 170 | return 171 | } 172 | 173 | // Return value unmarshaling 174 | tpd := typedParamDecoder{} 175 | ct := map[string]xdr.TypeDecoder{"libvirt.TypedParam": tpd} 176 | rdr := bytes.NewReader(r.Payload) 177 | dec := xdr.NewDecoderCustomTypes(rdr, 0, ct) 178 | // Dom: Domain 179 | _, err = dec.Decode(&rDom) 180 | if err != nil { 181 | return 182 | } 183 | 184 | return 185 | } 186 | 187 | // QEMUDomainAgentCommand is the go wrapper for QEMU_PROC_DOMAIN_AGENT_COMMAND. 188 | func (l *Libvirt) QEMUDomainAgentCommand(Dom Domain, Cmd string, Timeout int32, Flags uint32) (rResult OptString, err error) { 189 | var buf []byte 190 | 191 | args := QEMUDomainAgentCommandArgs { 192 | Dom: Dom, 193 | Cmd: Cmd, 194 | Timeout: Timeout, 195 | Flags: Flags, 196 | } 197 | 198 | buf, err = encode(&args) 199 | if err != nil { 200 | return 201 | } 202 | 203 | var r response 204 | r, err = l.requestStream(3, constants.QEMUProgram, buf, nil, nil) 205 | if err != nil { 206 | return 207 | } 208 | 209 | // Return value unmarshaling 210 | tpd := typedParamDecoder{} 211 | ct := map[string]xdr.TypeDecoder{"libvirt.TypedParam": tpd} 212 | rdr := bytes.NewReader(r.Payload) 213 | dec := xdr.NewDecoderCustomTypes(rdr, 0, ct) 214 | // Result: OptString 215 | _, err = dec.Decode(&rResult) 216 | if err != nil { 217 | return 218 | } 219 | 220 | return 221 | } 222 | 223 | // QEMUConnectDomainMonitorEventRegister is the go wrapper for QEMU_PROC_CONNECT_DOMAIN_MONITOR_EVENT_REGISTER. 224 | func (l *Libvirt) QEMUConnectDomainMonitorEventRegister(Dom OptDomain, Event OptString, Flags uint32) (rCallbackID int32, err error) { 225 | var buf []byte 226 | 227 | args := QEMUConnectDomainMonitorEventRegisterArgs { 228 | Dom: Dom, 229 | Event: Event, 230 | Flags: Flags, 231 | } 232 | 233 | buf, err = encode(&args) 234 | if err != nil { 235 | return 236 | } 237 | 238 | var r response 239 | r, err = l.requestStream(4, constants.QEMUProgram, buf, nil, nil) 240 | if err != nil { 241 | return 242 | } 243 | 244 | // Return value unmarshaling 245 | tpd := typedParamDecoder{} 246 | ct := map[string]xdr.TypeDecoder{"libvirt.TypedParam": tpd} 247 | rdr := bytes.NewReader(r.Payload) 248 | dec := xdr.NewDecoderCustomTypes(rdr, 0, ct) 249 | // CallbackID: int32 250 | _, err = dec.Decode(&rCallbackID) 251 | if err != nil { 252 | return 253 | } 254 | 255 | return 256 | } 257 | 258 | // QEMUConnectDomainMonitorEventDeregister is the go wrapper for QEMU_PROC_CONNECT_DOMAIN_MONITOR_EVENT_DEREGISTER. 259 | func (l *Libvirt) QEMUConnectDomainMonitorEventDeregister(CallbackID int32) (err error) { 260 | var buf []byte 261 | 262 | args := QEMUConnectDomainMonitorEventDeregisterArgs { 263 | CallbackID: CallbackID, 264 | } 265 | 266 | buf, err = encode(&args) 267 | if err != nil { 268 | return 269 | } 270 | 271 | 272 | _, err = l.requestStream(5, constants.QEMUProgram, buf, nil, nil) 273 | if err != nil { 274 | return 275 | } 276 | 277 | return 278 | } 279 | 280 | // QEMUDomainMonitorEvent is the go wrapper for QEMU_PROC_DOMAIN_MONITOR_EVENT. 281 | func (l *Libvirt) QEMUDomainMonitorEvent() (err error) { 282 | var buf []byte 283 | 284 | 285 | _, err = l.requestStream(6, constants.QEMUProgram, buf, nil, nil) 286 | if err != nil { 287 | return 288 | } 289 | 290 | return 291 | } 292 | 293 | -------------------------------------------------------------------------------- /rpc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The go-libvirt Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package libvirt 16 | 17 | import ( 18 | "bytes" 19 | "errors" 20 | "fmt" 21 | "io" 22 | "reflect" 23 | "strings" 24 | "sync/atomic" 25 | 26 | "github.com/digitalocean/go-libvirt/internal/constants" 27 | "github.com/digitalocean/go-libvirt/internal/event" 28 | xdr "github.com/digitalocean/go-libvirt/internal/go-xdr/xdr2" 29 | "github.com/digitalocean/go-libvirt/socket" 30 | ) 31 | 32 | // ErrUnsupported is returned if a procedure is not supported by libvirt 33 | var ErrUnsupported = errors.New("unsupported procedure requested") 34 | 35 | // ErrInterrupted is returned if the socket is closed while waiting for the 36 | // result of a procedure call. 37 | var ErrInterrupted = errors.New("procedure interrupted while awaiting response") 38 | 39 | // internal rpc response 40 | type response struct { 41 | Payload []byte 42 | Status uint32 43 | } 44 | 45 | // Error reponse from libvirt 46 | type Error struct { 47 | Code uint32 48 | Message string 49 | } 50 | 51 | func (e Error) Error() string { 52 | return e.Message 53 | } 54 | 55 | // checkError is used to check whether an error is a libvirtError, and if it is, 56 | // whether its error code matches the one passed in. It will return false if 57 | // these conditions are not met. 58 | func checkError(err error, expectedError ErrorNumber) bool { 59 | for err != nil { 60 | e, ok := err.(Error) 61 | if ok { 62 | return e.Code == uint32(expectedError) 63 | } 64 | err = errors.Unwrap(err) 65 | } 66 | return false 67 | } 68 | 69 | // IsNotFound detects libvirt's ERR_NO_DOMAIN. 70 | func IsNotFound(err error) bool { 71 | return checkError(err, ErrNoDomain) 72 | } 73 | 74 | // callback sends RPC responses to respective callers. 75 | func (l *Libvirt) callback(id int32, res response) { 76 | l.cmux.Lock() 77 | defer l.cmux.Unlock() 78 | 79 | c, ok := l.callbacks[id] 80 | if !ok { 81 | return 82 | } 83 | 84 | c <- res 85 | } 86 | 87 | // Route sends incoming packets to their listeners. 88 | func (l *Libvirt) Route(h *socket.Header, buf []byte) { 89 | // Route events to their respective listener 90 | var event event.Event 91 | 92 | switch h.Program { 93 | case constants.QEMUProgram: 94 | if h.Procedure != constants.QEMUProcDomainMonitorEvent { 95 | break 96 | } 97 | event = &DomainEvent{} 98 | case constants.Program: 99 | event = eventFromProcedureID(h.Procedure) 100 | } 101 | 102 | if event != nil { 103 | err := eventDecoder(buf, event) 104 | if err != nil { // event was malformed, drop. 105 | return 106 | } 107 | 108 | l.stream(event) 109 | return 110 | } 111 | 112 | // send response to caller 113 | l.callback(h.Serial, response{Payload: buf, Status: h.Status}) 114 | } 115 | 116 | func eventFromProcedureID(procID uint32) event.Event { 117 | switch procID { 118 | case constants.ProcDomainEventCallbackLifecycle: 119 | return &DomainEventCallbackLifecycleMsg{} 120 | case constants.ProcDomainEventCallbackReboot: 121 | return &DomainEventCallbackRebootMsg{} 122 | case constants.ProcDomainEventCallbackRtcChange: 123 | return &DomainEventCallbackRtcChangeMsg{} 124 | case constants.ProcDomainEventCallbackWatchdog: 125 | return &DomainEventCallbackWatchdogMsg{} 126 | case constants.ProcDomainEventCallbackIOError: 127 | return &DomainEventCallbackIOErrorMsg{} 128 | case constants.ProcDomainEventCallbackIOErrorReason: 129 | return &DomainEventCallbackIOErrorReasonMsg{} 130 | case constants.ProcDomainEventCallbackGraphics: 131 | return &DomainEventCallbackGraphicsMsg{} 132 | case constants.ProcDomainEventCallbackBlockJob: 133 | return &DomainEventCallbackBlockJobMsg{} 134 | case constants.ProcDomainEventCallbackDiskChange: 135 | return &DomainEventCallbackDiskChangeMsg{} 136 | case constants.ProcDomainEventCallbackTrayChange: 137 | return &DomainEventCallbackTrayChangeMsg{} 138 | case constants.ProcDomainEventCallbackPmwakeup: 139 | return &DomainEventCallbackPmwakeupMsg{} 140 | case constants.ProcDomainEventCallbackPmsuspend: 141 | return &DomainEventCallbackPmsuspendMsg{} 142 | case constants.ProcDomainEventCallbackBalloonChange: 143 | return &DomainEventCallbackBalloonChangeMsg{} 144 | case constants.ProcDomainEventCallbackPmsuspendDisk: 145 | return &DomainEventCallbackPmsuspendDiskMsg{} 146 | case constants.ProcDomainEventCallbackControlError: 147 | return &DomainEventCallbackControlErrorMsg{} 148 | case constants.ProcDomainEventCallbackDeviceRemoved: 149 | return &DomainEventCallbackDeviceRemovedMsg{} 150 | case constants.ProcDomainEventCallbackTunable: 151 | return &DomainEventCallbackTunableMsg{} 152 | case constants.ProcDomainEventCallbackDeviceAdded: 153 | return &DomainEventCallbackDeviceAddedMsg{} 154 | case constants.ProcDomainEventCallbackAgentLifecycle: 155 | return &DomainEventCallbackAgentLifecycleMsg{} 156 | case constants.ProcDomainEventCallbackMigrationIteration: 157 | return &DomainEventCallbackMigrationIterationMsg{} 158 | case constants.ProcDomainEventCallbackJobCompleted: 159 | return &DomainEventCallbackJobCompletedMsg{} 160 | case constants.ProcDomainEventCallbackDeviceRemovalFailed: 161 | return &DomainEventCallbackDeviceRemovalFailedMsg{} 162 | case constants.ProcDomainEventCallbackMetadataChange: 163 | return &DomainEventCallbackMetadataChangeMsg{} 164 | } 165 | return nil 166 | } 167 | 168 | // serial provides atomic access to the next sequential request serial number. 169 | func (l *Libvirt) serial() int32 { 170 | return atomic.AddInt32(&l.s, 1) 171 | } 172 | 173 | // stream decodes and relays domain events to their respective listener. 174 | func (l *Libvirt) stream(e event.Event) { 175 | l.emux.RLock() 176 | defer l.emux.RUnlock() 177 | 178 | q, ok := l.events[e.GetCallbackID()] 179 | if !ok { 180 | return 181 | } 182 | 183 | q.Push(e) 184 | } 185 | 186 | // addStream configures the routing for an event stream. 187 | func (l *Libvirt) addStream(s *event.Stream) { 188 | l.emux.Lock() 189 | defer l.emux.Unlock() 190 | 191 | l.events[s.CallbackID] = s 192 | } 193 | 194 | // removeStream deletes an event stream. The caller should first notify libvirt 195 | // to stop sending events for this stream. Subsequent calls to removeStream are 196 | // idempotent and return nil. 197 | func (l *Libvirt) removeStream(id int32) error { 198 | l.emux.Lock() 199 | defer l.emux.Unlock() 200 | 201 | // if the event is already removed, just return nil 202 | q, ok := l.events[id] 203 | if ok { 204 | delete(l.events, id) 205 | q.Shutdown() 206 | } 207 | 208 | return nil 209 | } 210 | 211 | // removeAllStreams deletes all event streams. This is meant to be used to 212 | // clean up only once the underlying connection to libvirt is disconnected and 213 | // thus does not attempt to notify libvirt to stop sending events. 214 | func (l *Libvirt) removeAllStreams() { 215 | l.emux.Lock() 216 | defer l.emux.Unlock() 217 | 218 | for _, ev := range l.events { 219 | ev.Shutdown() 220 | delete(l.events, ev.CallbackID) 221 | } 222 | } 223 | 224 | // register configures a method response callback 225 | func (l *Libvirt) register(id int32, c chan response) { 226 | l.cmux.Lock() 227 | defer l.cmux.Unlock() 228 | 229 | l.callbacks[id] = c 230 | } 231 | 232 | // deregister destroys a method response callback. It is the responsibility of 233 | // the caller to manage locking (l.cmux) during this call. 234 | func (l *Libvirt) deregister(id int32) { 235 | _, ok := l.callbacks[id] 236 | if !ok { 237 | return 238 | } 239 | 240 | close(l.callbacks[id]) 241 | delete(l.callbacks, id) 242 | } 243 | 244 | // deregisterAll closes all waiting callback channels. This is used to clean up 245 | // if the connection to libvirt is lost. Callers waiting for responses will 246 | // return an error when the response channel is closed, rather than just 247 | // hanging. 248 | func (l *Libvirt) deregisterAll() { 249 | l.cmux.Lock() 250 | defer l.cmux.Unlock() 251 | 252 | for id := range l.callbacks { 253 | l.deregister(id) 254 | } 255 | } 256 | 257 | // request performs a libvirt RPC request. 258 | // returns response returned by server. 259 | // if response is not OK, decodes error from it and returns it. 260 | func (l *Libvirt) request(proc uint32, program uint32, payload []byte) (response, error) { 261 | return l.requestStream(proc, program, payload, nil, nil) 262 | } 263 | 264 | // requestStream performs a libvirt RPC request. The `out` and `in` parameters 265 | // are optional, and should be nil when RPC endpoints don't return a stream. 266 | func (l *Libvirt) requestStream(proc uint32, program uint32, payload []byte, 267 | out io.Reader, in io.Writer) (response, error) { 268 | serial := l.serial() 269 | c := make(chan response) 270 | 271 | l.register(serial, c) 272 | defer func() { 273 | l.cmux.Lock() 274 | defer l.cmux.Unlock() 275 | 276 | l.deregister(serial) 277 | }() 278 | 279 | err := l.socket.SendPacket(serial, proc, program, payload, socket.Call, 280 | socket.StatusOK) 281 | if err != nil { 282 | return response{}, err 283 | } 284 | 285 | resp, err := l.getResponse(c) 286 | if err != nil { 287 | return resp, err 288 | } 289 | 290 | if out != nil { 291 | abort := make(chan bool) 292 | outErr := make(chan error) 293 | go func() { 294 | outErr <- l.socket.SendStream(serial, proc, program, out, abort) 295 | }() 296 | 297 | // Even without incoming stream server sends confirmation once all data is received 298 | resp, err = l.processIncomingStream(c, in) 299 | if err != nil { 300 | abort <- true 301 | return resp, err 302 | } 303 | 304 | err = <-outErr 305 | if err != nil { 306 | return response{}, err 307 | } 308 | } 309 | 310 | switch in { 311 | case nil: 312 | return resp, nil 313 | default: 314 | return l.processIncomingStream(c, in) 315 | } 316 | } 317 | 318 | // processIncomingStream is called once we've successfully sent a request to 319 | // libvirt. It writes the responses back to the stream passed by the caller 320 | // until libvirt sends a packet with statusOK or an error. 321 | func (l *Libvirt) processIncomingStream(c chan response, inStream io.Writer) (response, error) { 322 | for { 323 | resp, err := l.getResponse(c) 324 | if err != nil { 325 | return resp, err 326 | } 327 | 328 | // StatusOK indicates end of stream 329 | if resp.Status == socket.StatusOK { 330 | return resp, nil 331 | } 332 | 333 | // FIXME: this smells. 334 | // StatusError is handled in getResponse, so this must be StatusContinue 335 | // StatusContinue is only valid here for stream packets 336 | // libvirtd breaks protocol and returns StatusContinue with an 337 | // empty response Payload when the stream finishes 338 | if len(resp.Payload) == 0 { 339 | return resp, nil 340 | } 341 | if inStream != nil { 342 | _, err = inStream.Write(resp.Payload) 343 | if err != nil { 344 | return response{}, err 345 | } 346 | } 347 | } 348 | } 349 | 350 | func (l *Libvirt) getResponse(c chan response) (response, error) { 351 | resp, ok := <-c 352 | 353 | if !ok { 354 | // The channel was closed before a response was received. This means 355 | // that the socket was unexpectedly closed during the RPC call. In 356 | // this case, we must assume the worst, such as libvirt crashed while 357 | // attempting to execute the call. 358 | return resp, ErrInterrupted 359 | } 360 | 361 | if resp.Status == socket.StatusError { 362 | return resp, decodeError(resp.Payload) 363 | } 364 | 365 | return resp, nil 366 | } 367 | 368 | // encode XDR encodes the provided data. 369 | func encode(data interface{}) ([]byte, error) { 370 | var buf bytes.Buffer 371 | _, err := xdr.Marshal(&buf, data) 372 | 373 | return buf.Bytes(), err 374 | } 375 | 376 | // decodeError extracts an error message from the provider buffer. 377 | func decodeError(buf []byte) error { 378 | dec := xdr.NewDecoder(bytes.NewReader(buf)) 379 | 380 | e := struct { 381 | Code uint32 382 | DomainID uint32 383 | Padding uint8 384 | Message string 385 | Level uint32 386 | }{} 387 | _, err := dec.Decode(&e) 388 | if err != nil { 389 | return err 390 | } 391 | 392 | if strings.Contains(e.Message, "unknown procedure") { 393 | return ErrUnsupported 394 | } 395 | 396 | // if libvirt returns ERR_OK, ignore the error 397 | if ErrorNumber(e.Code) == ErrOk { 398 | return nil 399 | } 400 | 401 | return Error{Code: uint32(e.Code), Message: e.Message} 402 | } 403 | 404 | // eventDecoder decodes an event from a xdr buffer. 405 | func eventDecoder(buf []byte, e interface{}) error { 406 | dec := xdr.NewDecoder(bytes.NewReader(buf)) 407 | _, err := dec.Decode(e) 408 | return err 409 | } 410 | 411 | type typedParamDecoder struct{} 412 | 413 | // Decode decodes a TypedParam. These are part of the libvirt spec, and not xdr 414 | // proper. TypedParams contain a name, which is called Field for some reason, 415 | // and a Value, which itself has a "discriminant" - an integer enum encoding the 416 | // actual type, and a value, the length of which varies based on the actual 417 | // type. 418 | func (tpd typedParamDecoder) Decode(d *xdr.Decoder, v reflect.Value) (int, error) { 419 | // Get the name of the typed param first 420 | name, n, err := d.DecodeString() 421 | if err != nil { 422 | return n, err 423 | } 424 | val, n2, err := tpd.decodeTypedParamValue(d) 425 | n += n2 426 | if err != nil { 427 | return n, err 428 | } 429 | tp := &TypedParam{Field: name, Value: *val} 430 | v.Set(reflect.ValueOf(*tp)) 431 | 432 | return n, nil 433 | } 434 | 435 | // decodeTypedParamValue decodes the Value part of a TypedParam. 436 | func (typedParamDecoder) decodeTypedParamValue(d *xdr.Decoder) (*TypedParamValue, int, error) { 437 | // All TypedParamValues begin with a uint32 discriminant that tells us what 438 | // type they are. 439 | discriminant, n, err := d.DecodeUint() 440 | if err != nil { 441 | return nil, n, err 442 | } 443 | var n2 int 444 | var tpv *TypedParamValue 445 | switch discriminant { 446 | case 1: 447 | var val int32 448 | n2, err = d.Decode(&val) 449 | tpv = &TypedParamValue{D: discriminant, I: val} 450 | case 2: 451 | var val uint32 452 | n2, err = d.Decode(&val) 453 | tpv = &TypedParamValue{D: discriminant, I: val} 454 | case 3: 455 | var val int64 456 | n2, err = d.Decode(&val) 457 | tpv = &TypedParamValue{D: discriminant, I: val} 458 | case 4: 459 | var val uint64 460 | n2, err = d.Decode(&val) 461 | tpv = &TypedParamValue{D: discriminant, I: val} 462 | case 5: 463 | var val float64 464 | n2, err = d.Decode(&val) 465 | tpv = &TypedParamValue{D: discriminant, I: val} 466 | case 6: 467 | var val int32 468 | n2, err = d.Decode(&val) 469 | tpv = &TypedParamValue{D: discriminant, I: val} 470 | case 7: 471 | var val string 472 | n2, err = d.Decode(&val) 473 | tpv = &TypedParamValue{D: discriminant, I: val} 474 | 475 | default: 476 | err = fmt.Errorf("invalid parameter type %v", discriminant) 477 | } 478 | n += n2 479 | 480 | return tpv, n, err 481 | } 482 | -------------------------------------------------------------------------------- /rpc_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The go-libvirt Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package libvirt 16 | 17 | import ( 18 | "bytes" 19 | "fmt" 20 | "sync" 21 | "testing" 22 | 23 | "github.com/digitalocean/go-libvirt/internal/constants" 24 | "github.com/digitalocean/go-libvirt/internal/event" 25 | xdr "github.com/digitalocean/go-libvirt/internal/go-xdr/xdr2" 26 | "github.com/digitalocean/go-libvirt/libvirttest" 27 | "github.com/digitalocean/go-libvirt/socket" 28 | "github.com/stretchr/testify/assert" 29 | ) 30 | 31 | var ( 32 | // dc229f87d4de47198cfd2e21c6105b01 33 | testUUID = [UUIDBuflen]byte{ 34 | 0xdc, 0x22, 0x9f, 0x87, 0xd4, 0xde, 0x47, 0x19, 35 | 0x8c, 0xfd, 0x2e, 0x21, 0xc6, 0x10, 0x5b, 0x01, 36 | } 37 | 38 | testEventHeader = []byte{ 39 | 0x00, 0x00, 0x00, 0xb0, // length 40 | 0x20, 0x00, 0x80, 0x87, // program 41 | 0x00, 0x00, 0x00, 0x01, // version 42 | 0x00, 0x00, 0x00, 0x06, // procedure 43 | 0x00, 0x00, 0x00, 0x01, // type 44 | 0x00, 0x00, 0x00, 0x00, // serial 45 | 0x00, 0x00, 0x00, 0x00, // status 46 | } 47 | 48 | testEvent = []byte{ 49 | 0x00, 0x00, 0x00, 0x01, // callback id 50 | 51 | // domain name ("test") 52 | 0x00, 0x00, 0x00, 0x04, 0x74, 0x65, 0x73, 0x74, 53 | 54 | // uuid (dc229f87d4de47198cfd2e21c6105b01) 55 | 0xdc, 0x22, 0x9f, 0x87, 0xd4, 0xde, 0x47, 0x19, 56 | 0x8c, 0xfd, 0x2e, 0x21, 0xc6, 0x10, 0x5b, 0x01, 57 | 58 | // domain id (14) 59 | 0x00, 0x00, 0x00, 0x0e, 60 | 61 | // event name (BLOCK_JOB_COMPLETED) 62 | 0x00, 0x00, 0x00, 0x13, 0x42, 0x4c, 0x4f, 0x43, 63 | 0x4b, 0x5f, 0x4a, 0x4f, 0x42, 0x5f, 0x43, 0x4f, 64 | 0x4d, 0x50, 0x4c, 0x45, 0x54, 0x45, 0x44, 0x00, 65 | 66 | // seconds (1462211891) 67 | 0x00, 0x00, 0x00, 0x00, 0x57, 0x27, 0x95, 0x33, 68 | 69 | // microseconds (931791) 70 | 0x00, 0x0e, 0x37, 0xcf, 71 | 72 | // event json data 73 | // ({"device":"drive-ide0-0-0","len":0,"offset":0,"speed":0,"type":"commit"}) 74 | 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x48, 75 | 0x7b, 0x22, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 76 | 0x22, 0x3a, 0x22, 0x64, 0x72, 0x69, 0x76, 0x65, 77 | 0x2d, 0x69, 0x64, 0x65, 0x30, 0x2d, 0x30, 0x2d, 78 | 0x30, 0x22, 0x2c, 0x22, 0x6c, 0x65, 0x6e, 0x22, 79 | 0x3a, 0x30, 0x2c, 0x22, 0x6f, 0x66, 0x66, 0x73, 80 | 0x65, 0x74, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x73, 81 | 0x70, 0x65, 0x65, 0x64, 0x22, 0x3a, 0x30, 0x2c, 82 | 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3a, 0x22, 83 | 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x22, 0x7d, 84 | } 85 | 86 | testLifeCycle = []byte{ 87 | 0x00, 0x00, 0x00, 0x01, // callback id 88 | 89 | // domain name ("test") 90 | 0x00, 0x00, 0x00, 0x04, 0x74, 0x65, 0x73, 0x74, 91 | 92 | // event data 93 | 0x00, 0x00, 0x00, 0x50, 0xad, 0xf7, 0x3f, 0xbe, 0xca, 0x48, 0xac, 0x95, 94 | 0x13, 0x8a, 0x31, 0xf4, 0xfe, 0x03, 0x2a, 0xff, 0xff, 0xff, 0xff, 0x00, 95 | 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 96 | } 97 | 98 | testErrorMessage = []byte{ 99 | 0x00, 0x00, 0x00, 0x37, // code (55, errOperationInvalid) 100 | 0x00, 0x00, 0x00, 0x0a, // domain id 101 | 102 | // message ("Requested operation is not valid: domain is not running") 103 | 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x37, 104 | 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 105 | 0x64, 0x20, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 106 | 0x69, 0x6f, 0x6e, 0x20, 0x69, 0x73, 0x20, 0x6e, 107 | 0x6f, 0x74, 0x20, 0x76, 0x61, 0x6c, 0x69, 0x64, 108 | 0x3a, 0x20, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 109 | 0x20, 0x69, 0x73, 0x20, 0x6e, 0x6f, 0x74, 0x20, 110 | 0x72, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x00, 111 | 112 | // error level 113 | 0x00, 0x00, 0x00, 0x02, 114 | } 115 | 116 | testErrorNotFoundMessage = []byte{ 117 | 0x00, 0x00, 0x00, 0x2a, // code (42 errDoDmain) 118 | 0x00, 0x00, 0x00, 0x0a, // domain id 119 | 120 | // message 121 | 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x38, 122 | 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x20, 0x6e, 123 | 0x6f, 0x74, 0x20, 0x66, 0x6f, 0x75, 0x6e, 0x64, 124 | 0x3a, 0x20, 0x6e, 0x6f, 0x20, 0x64, 0x6f, 0x6d, 125 | 0x61, 0x69, 0x6e, 0x20, 0x77, 0x69, 0x74, 0x68, 126 | 0x20, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 127 | 0x67, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x27, 128 | 0x74, 0x65, 0x73, 0x74, 0x2d, 0x2d, 0x2d, 0x27, 129 | 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 130 | 131 | // error level 132 | 0x00, 0x00, 0x00, 0x01, 133 | } 134 | ) 135 | 136 | func TestDecodeEvent(t *testing.T) { 137 | var e DomainEvent 138 | err := eventDecoder(testEvent, &e) 139 | if err != nil { 140 | t.Error(err) 141 | } 142 | 143 | expCbID := int32(1) 144 | if e.CallbackID != expCbID { 145 | t.Errorf("expected callback id %d, got %d", expCbID, e.CallbackID) 146 | } 147 | 148 | expName := "test" 149 | if e.Domain.Name != expName { 150 | t.Errorf("expected domain %s, got %s", expName, e.Domain.Name) 151 | } 152 | 153 | expUUID := testUUID 154 | if !bytes.Equal(e.Domain.UUID[:], expUUID[:]) { 155 | t.Errorf("expected uuid:\t%x, got\n\t\t\t%x", expUUID, e.Domain.UUID) 156 | } 157 | 158 | expID := int32(14) 159 | if e.Domain.ID != expID { 160 | t.Errorf("expected id %d, got %d", expID, e.Domain.ID) 161 | } 162 | 163 | expEvent := "BLOCK_JOB_COMPLETED" 164 | if e.Event != expEvent { 165 | t.Errorf("expected %s, got %s", expEvent, e.Event) 166 | } 167 | 168 | expSec := uint64(1462211891) 169 | if e.Seconds != expSec { 170 | t.Errorf("expected seconds to be %d, got %d", expSec, e.Seconds) 171 | } 172 | 173 | expMs := uint32(931791) 174 | if e.Microseconds != expMs { 175 | t.Errorf("expected microseconds to be %d, got %d", expMs, e.Microseconds) 176 | } 177 | 178 | expDetails := []byte(`{"device":"drive-ide0-0-0","len":0,"offset":0,"speed":0,"type":"commit"}`) 179 | if e.Domain.ID != expID { 180 | t.Errorf("expected data %s, got %s", expDetails, e.Details) 181 | } 182 | } 183 | 184 | func TestDecodeError(t *testing.T) { 185 | expectedMsg := "Requested operation is not valid: domain is not running" 186 | expectedCode := ErrOperationInvalid 187 | 188 | err := decodeError(testErrorMessage) 189 | e := err.(Error) 190 | if e.Message != expectedMsg { 191 | t.Errorf("expected error message %s, got %s", expectedMsg, err.Error()) 192 | } 193 | if e.Code != uint32(expectedCode) { 194 | t.Errorf("expected code %d, got %d", expectedCode, e.Code) 195 | } 196 | } 197 | 198 | func TestErrNotFound(t *testing.T) { 199 | err := decodeError(testErrorNotFoundMessage) 200 | ok := IsNotFound(err) 201 | if !ok { 202 | t.Errorf("expected true, got %t", ok) 203 | } 204 | 205 | err = fmt.Errorf("something went wrong: %w", err) 206 | ok = IsNotFound(err) 207 | if !ok { 208 | t.Errorf("expected true, got %t", ok) 209 | } 210 | } 211 | 212 | func TestEncode(t *testing.T) { 213 | data := "test" 214 | buf, err := encode(data) 215 | if err != nil { 216 | t.Error(err) 217 | } 218 | 219 | dec := xdr.NewDecoder(bytes.NewReader(buf)) 220 | res, _, err := dec.DecodeString() 221 | if err != nil { 222 | t.Error(err) 223 | } 224 | 225 | if res != data { 226 | t.Errorf("expected %s, got %s", data, res) 227 | } 228 | } 229 | 230 | func TestRegister(t *testing.T) { 231 | l := &Libvirt{} 232 | l.callbacks = make(map[int32]chan response) 233 | id := int32(1) 234 | c := make(chan response) 235 | 236 | l.register(id, c) 237 | if _, ok := l.callbacks[id]; !ok { 238 | t.Error("expected callback to register") 239 | } 240 | } 241 | 242 | func TestDeregister(t *testing.T) { 243 | id := int32(1) 244 | 245 | l := &Libvirt{} 246 | l.callbacks = map[int32]chan response{ 247 | id: make(chan response), 248 | } 249 | 250 | l.deregister(id) 251 | if _, ok := l.callbacks[id]; ok { 252 | t.Error("expected callback to deregister") 253 | } 254 | } 255 | 256 | func TestAddStream(t *testing.T) { 257 | id := int32(1) 258 | 259 | l := &Libvirt{} 260 | l.events = make(map[int32]*event.Stream) 261 | 262 | stream := event.NewStream(0, id) 263 | defer stream.Shutdown() 264 | 265 | l.addStream(stream) 266 | if _, ok := l.events[id]; !ok { 267 | t.Error("expected event stream to exist") 268 | } 269 | } 270 | 271 | func TestRemoveStream(t *testing.T) { 272 | id := int32(1) 273 | 274 | dialer := libvirttest.New() 275 | l := NewWithDialer(dialer) 276 | 277 | err := l.Connect() 278 | if err != nil { 279 | t.Fatalf("connect failed: %v", err) 280 | } 281 | defer l.Disconnect() 282 | 283 | stream := event.NewStream(constants.QEMUProgram, id) 284 | defer stream.Shutdown() 285 | 286 | l.addStream(stream) 287 | 288 | fmt.Println("removing stream") 289 | err = l.removeStream(id) 290 | if err != nil { 291 | t.Fatal(err) 292 | } 293 | 294 | if _, ok := l.events[id]; ok { 295 | t.Error("expected event stream to be removed") 296 | } 297 | } 298 | 299 | func TestRemoveAllStreams(t *testing.T) { 300 | id1 := int32(1) 301 | id2 := int32(2) 302 | 303 | dialer := libvirttest.New() 304 | l := NewWithDialer(dialer) 305 | 306 | err := l.Connect() 307 | if err != nil { 308 | t.Fatalf("connect failed: %v", err) 309 | } 310 | defer l.Disconnect() 311 | 312 | // verify it's a successful no-op when no streams have been added 313 | l.removeAllStreams() 314 | if len(l.events) != 0 { 315 | t.Fatal("expected no streams after remove all") 316 | } 317 | 318 | stream := event.NewStream(constants.QEMUProgram, id1) 319 | defer stream.Shutdown() 320 | 321 | l.addStream(stream) 322 | 323 | stream2 := event.NewStream(constants.QEMUProgram, id2) 324 | defer stream2.Shutdown() 325 | 326 | l.addStream(stream2) 327 | 328 | l.removeAllStreams() 329 | 330 | if len(l.events) != 0 { 331 | t.Error("expected all event streams to be removed") 332 | } 333 | } 334 | 335 | func TestStream(t *testing.T) { 336 | id := int32(1) 337 | stream := event.NewStream(constants.Program, 1) 338 | defer stream.Shutdown() 339 | 340 | l := &Libvirt{} 341 | l.events = map[int32]*event.Stream{ 342 | id: stream, 343 | } 344 | 345 | var streamEvent DomainEvent 346 | err := eventDecoder(testEvent, &streamEvent) 347 | if err != nil { // event was malformed, drop. 348 | t.Error(err) 349 | } 350 | 351 | l.stream(streamEvent) 352 | e := <-stream.Recv() 353 | 354 | if e.(DomainEvent).Event != "BLOCK_JOB_COMPLETED" { 355 | t.Error("expected event") 356 | } 357 | } 358 | 359 | func TestSerial(t *testing.T) { 360 | count := int32(10) 361 | l := &Libvirt{} 362 | 363 | var wg sync.WaitGroup 364 | for i := 0; i < 10; i++ { 365 | wg.Add(1) 366 | go func() { 367 | l.serial() 368 | wg.Done() 369 | }() 370 | } 371 | 372 | wg.Wait() 373 | 374 | expected := count + int32(1) 375 | actual := l.serial() 376 | if expected != actual { 377 | t.Errorf("expected serial to be %d, got %d", expected, actual) 378 | } 379 | } 380 | 381 | func TestLookup(t *testing.T) { 382 | name := "test" 383 | 384 | dialer := libvirttest.New() 385 | l := NewWithDialer(dialer) 386 | 387 | err := l.Connect() 388 | if err != nil { 389 | t.Fatalf("connect failed: %v", err) 390 | } 391 | defer l.Disconnect() 392 | 393 | d, err := l.lookup(name) 394 | if err != nil { 395 | t.Error(err) 396 | } 397 | 398 | if d.Name != name { 399 | t.Errorf("expected domain %s, got %s", name, d.Name) 400 | } 401 | } 402 | 403 | func TestDeregisterAll(t *testing.T) { 404 | dialer := libvirttest.New() 405 | c1 := make(chan response) 406 | c2 := make(chan response) 407 | l := NewWithDialer(dialer) 408 | if len(l.callbacks) != 0 { 409 | t.Error("expected callback map to be empty at test start") 410 | } 411 | l.register(1, c1) 412 | l.register(2, c2) 413 | if len(l.callbacks) != 2 { 414 | t.Error("expected callback map to have 2 entries after inserts") 415 | } 416 | l.deregisterAll() 417 | if len(l.callbacks) != 0 { 418 | t.Error("expected callback map to be empty after deregisterAll") 419 | } 420 | } 421 | 422 | // TestRouteDeadlock ensures that go-libvirt doesn't hang when trying to send 423 | // both an event and a response (to a request) at the same time. 424 | // 425 | // Events are inherently asynchronous - the client may not be ready to receive 426 | // an event when it arrives. We don't want that to prevent go-libvirt from 427 | // continuing to receive responses to outstanding requests. This test checks for 428 | // deadlocks where the client doesn't immediately consume incoming events. 429 | func TestRouteDeadlock(t *testing.T) { 430 | id := int32(1) 431 | rch := make(chan response, 1) 432 | 433 | l := &Libvirt{ 434 | callbacks: map[int32]chan response{ 435 | id: rch, 436 | }, 437 | events: make(map[int32]*event.Stream), 438 | } 439 | stream := event.NewStream(constants.Program, id) 440 | 441 | l.addStream(stream) 442 | 443 | respHeader := &socket.Header{ 444 | Program: constants.Program, 445 | Serial: id, 446 | Status: socket.StatusOK, 447 | } 448 | eventHeader := &socket.Header{ 449 | Program: constants.Program, 450 | Procedure: constants.ProcDomainEventCallbackLifecycle, 451 | Status: socket.StatusOK, 452 | } 453 | 454 | send := func(respCount, evCount int) { 455 | // Send the events first 456 | for i := 0; i < evCount; i++ { 457 | l.Route(eventHeader, testLifeCycle) 458 | } 459 | // Now send the requests. 460 | for i := 0; i < respCount; i++ { 461 | l.Route(respHeader, []byte{}) 462 | } 463 | } 464 | 465 | cases := []struct{ rCount, eCount int }{ 466 | {2, 0}, 467 | {0, 2}, 468 | {1, 1}, 469 | {2, 2}, 470 | {50, 50}, 471 | } 472 | 473 | for _, tc := range cases { 474 | fmt.Printf("testing %d responses and %d events\n", tc.rCount, tc.eCount) 475 | go send(tc.rCount, tc.eCount) 476 | 477 | for i := 0; i < tc.rCount; i++ { 478 | r := <-rch 479 | assert.Equal(t, r.Status, uint32(socket.StatusOK)) 480 | } 481 | for i := 0; i < tc.eCount; i++ { 482 | e := <-stream.Recv() 483 | fmt.Printf("event %v/%v received\n", i, len(cases)) 484 | assert.Equal(t, "test", e.(*DomainEventCallbackLifecycleMsg).Msg.Dom.Name) 485 | } 486 | } 487 | 488 | // finally verify that canceling the context doesn't cause a deadlock. 489 | fmt.Println("checking for deadlock after context cancellation") 490 | send(0, 50) 491 | } 492 | 493 | func TestGetResponseInterrupted(t *testing.T) { 494 | dialer := libvirttest.New() 495 | l := NewWithDialer(dialer) 496 | c := make(chan response) 497 | close(c) 498 | _, err := l.getResponse(c) 499 | assert.Equal(t, ErrInterrupted, err) 500 | } 501 | -------------------------------------------------------------------------------- /scripts/gen-consts.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This runs the first code generator used by go-libvirt: c-for-go. This script 4 | # is run from the 'go generate ./...' command, and only needs to be run when 5 | # changing to a different version of libvirt. 6 | 7 | # Set TOP to the root of this repo, and SCRIPTS to the absolute path to the 8 | # scripts/ directory. 9 | TOP="$( cd "$( dirname "${BASH_SOURCE[0]}" )/.." && pwd )" 10 | SCRIPTS="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 11 | 12 | if [ -z "${LIBVIRT_SOURCE}" ]; then 13 | echo "Set LIBVIRT_SOURCE to the root of the libvirt sources you want to use first." 14 | exit 1 15 | fi 16 | if [ -z "$GOBIN" ]; then 17 | export GOBIN="$TOP/bin" 18 | fi 19 | 20 | # Ensure this specific tooling comes first on the PATH 21 | export PATH="$GOBIN:$PATH" 22 | 23 | # Make sure c-for-go is installed 24 | echo "Attempting to install c-for-go..." 25 | if ! go install github.com/xlab/c-for-go@v1.1.0 ; then 26 | echo "failed to install c-for-go." 27 | exit 1 28 | fi 29 | 30 | # Make sure goyacc is installed (needed for the lvgen/ generator) 31 | echo "Attempting to install goyacc..." 32 | if ! go install golang.org/x/tools/cmd/goyacc@v0.18.0; then 33 | echo "failed to install goyacc. Please install it manually from https://golang.org/x/tools/cmd/goyacc" 34 | exit 1 35 | fi 36 | 37 | # Ensure fresh output is in libvirt/ 38 | rm -rf libvirt/ 39 | 40 | # Temporarily symlink the libvirt sources to a subdirectory because c-for-go 41 | # lacks a mechanism for us to pass it a search path for header files. 42 | LVDIR=lv_source 43 | ln -sF "${LIBVIRT_SOURCE}" "${LVDIR}" 44 | if ! c-for-go -nostamp -nocgo -ccincl libvirt.yml; then 45 | echo "c-for-go failed" 46 | exit 1 47 | fi 48 | 49 | # Use the generated 'const.go' 50 | mv libvirt/const.go "${SCRIPTS}/../const.gen.go" 51 | 52 | # Remove unused generated files 53 | rm -f \ 54 | libvirt/cgo_helpers.go \ 55 | libvirt/doc.go \ 56 | libvirt/types.go 57 | 58 | rm "${LVDIR}" 59 | -------------------------------------------------------------------------------- /scripts/licensecheck.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Verify that the correct license block is present in all Go source 4 | # files. 5 | read -r -d '' EXPECTED < 0 { 215 | return ssh.PublicKeysCallback(am.getSigners) 216 | } 217 | } 218 | return nil 219 | }) 220 | return am 221 | } 222 | 223 | // Password adds the password auth method to the allowed list. 224 | func (am *SSHAuthMethods) Password() *SSHAuthMethods { 225 | am.authMethodGenerators = append(am.authMethodGenerators, 226 | func(s *SSH) ssh.AuthMethod { 227 | if s.password == "" { 228 | am.errors = append(am.errors, 229 | errors.New("no ssh password set")) 230 | return nil 231 | } 232 | return ssh.Password(s.password) 233 | }) 234 | return am 235 | } 236 | 237 | // KeyboardInteractive adds the keyboard-interactive auth method to the 238 | // allowed list (currently unimplemented). 239 | func (am *SSHAuthMethods) KeyboardInteractive() *SSHAuthMethods { 240 | // Not implemented 241 | return am 242 | } 243 | 244 | func (am *SSHAuthMethods) getSigners() ([]ssh.Signer, error) { 245 | return am.signers, nil 246 | } 247 | 248 | func (am *SSHAuthMethods) authMethods(s *SSH) []ssh.AuthMethod { 249 | am.signers = nil 250 | am.errors = nil 251 | methods := []ssh.AuthMethod{} 252 | for _, g := range am.authMethodGenerators { 253 | if m := g(s); m != nil { 254 | methods = append(methods, m) 255 | } 256 | } 257 | return methods 258 | } 259 | 260 | // NewSSH returns an ssh dialer for connecting to libvirt running on another 261 | // server. 262 | func NewSSH(hostAddr string, opts ...SSHOption) *SSH { 263 | defaultUsername := "" 264 | if currentUser, err := user.Current(); err == nil { 265 | defaultUsername = currentUser.Username 266 | } 267 | 268 | s := &SSH{ 269 | dialTimeout: defaultSSHTimeout, 270 | username: defaultUsername, 271 | hostname: hostAddr, 272 | port: defaultSSHPort, 273 | remoteSocket: defaultSocket, 274 | knownHostsFile: defaultSSHKnownHostsFile(), 275 | keyFile: defaultSSHKeyFile(), 276 | authMethods: (&SSHAuthMethods{}).Agent().PrivKey().Password().KeyboardInteractive(), 277 | } 278 | 279 | for _, opt := range opts { 280 | opt(s) 281 | } 282 | 283 | return s 284 | } 285 | 286 | func appendKnownHost(knownHostsFile string, host string, key ssh.PublicKey) { 287 | if err := os.MkdirAll(filepath.Dir(knownHostsFile), 0700); err != nil { 288 | return 289 | } 290 | 291 | f, err := os.OpenFile(knownHostsFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600) 292 | if err != nil { 293 | return 294 | } 295 | defer f.Close() 296 | 297 | fmt.Fprintf(f, "%s\n", knownhosts.Line([]string{host}, key)) 298 | } 299 | 300 | func (s *SSH) checkHostKey(host string, remote net.Addr, key ssh.PublicKey) error { 301 | checkKnown, err := knownhosts.New(s.knownHostsFile) 302 | if err != nil { 303 | if errors.Is(err, fs.ErrNotExist) && s.acceptUnknownHostKey { 304 | appendKnownHost(s.knownHostsFile, host, key) 305 | return nil 306 | } 307 | return err 308 | } 309 | 310 | result := checkKnown(host, remote, key) 311 | if keyErr, ok := result.(*knownhosts.KeyError); ok { 312 | if len(keyErr.Want) == 0 && s.acceptUnknownHostKey { 313 | appendKnownHost(s.knownHostsFile, host, key) 314 | return nil 315 | } 316 | } 317 | return result 318 | } 319 | 320 | func (s *SSH) config() (*ssh.ClientConfig, error) { 321 | hostKeyCallback := s.checkHostKey 322 | if s.insecureIgnoreHostKey { 323 | hostKeyCallback = ssh.InsecureIgnoreHostKey() //nolint:gosec 324 | } 325 | return &ssh.ClientConfig{ 326 | User: s.username, 327 | HostKeyCallback: hostKeyCallback, 328 | Auth: s.authMethods.authMethods(s), 329 | Timeout: s.dialTimeout, 330 | }, nil 331 | } 332 | 333 | // Dial connects to libvirt running on another server over ssh. 334 | func (s *SSH) Dial() (net.Conn, error) { 335 | conf, err := s.config() 336 | if err != nil { 337 | return nil, err 338 | } 339 | 340 | sshClient, err := ssh.Dial("tcp", net.JoinHostPort(s.hostname, s.port), 341 | conf) 342 | if err != nil { 343 | if strings.HasPrefix(err.Error(), "ssh: handshake failed: ssh: unable to authenticate") { 344 | err = errors.Join(append([]error{err}, s.authMethods.errors...)...) 345 | } 346 | return nil, err 347 | } 348 | c, err := sshClient.Dial("unix", s.remoteSocket) 349 | if err != nil { 350 | return nil, fmt.Errorf("failed to connect to remote libvirt socket %s: %w", s.remoteSocket, err) 351 | } 352 | 353 | return c, nil 354 | } 355 | -------------------------------------------------------------------------------- /socket/dialers/local.go: -------------------------------------------------------------------------------- 1 | package dialers 2 | 3 | import ( 4 | "net" 5 | "time" 6 | ) 7 | 8 | const ( 9 | // defaultSocket specifies the default path to the libvirt unix socket. 10 | defaultSocket = "/var/run/libvirt/libvirt-sock" 11 | 12 | // defaultLocalTimeout specifies the default libvirt dial timeout. 13 | defaultLocalTimeout = 15 * time.Second 14 | ) 15 | 16 | // Local implements connecting to a local libvirtd over the unix socket. 17 | type Local struct { 18 | timeout time.Duration 19 | socket string 20 | } 21 | 22 | // LocalOption is a function for setting local socket options. 23 | type LocalOption func(*Local) 24 | 25 | // WithLocalTimeout sets the dial timeout. 26 | func WithLocalTimeout(timeout time.Duration) LocalOption { 27 | return func(l *Local) { 28 | l.timeout = timeout 29 | } 30 | } 31 | 32 | // WithSocket sets the path to the local libvirt socket. 33 | func WithSocket(socket string) LocalOption { 34 | return func(l *Local) { 35 | l.socket = socket 36 | } 37 | } 38 | 39 | // NewLocal is a default dialer to simply connect to a locally running libvirt's 40 | // socket. 41 | func NewLocal(opts ...LocalOption) *Local { 42 | l := &Local{ 43 | timeout: defaultLocalTimeout, 44 | socket: defaultSocket, 45 | } 46 | 47 | for _, opt := range opts { 48 | opt(l) 49 | } 50 | 51 | return l 52 | } 53 | 54 | // Dial connects to a local socket 55 | func (l *Local) Dial() (net.Conn, error) { 56 | return net.DialTimeout("unix", l.socket, l.timeout) 57 | } 58 | -------------------------------------------------------------------------------- /socket/dialers/remote.go: -------------------------------------------------------------------------------- 1 | package dialers 2 | 3 | import ( 4 | "net" 5 | "time" 6 | ) 7 | 8 | const ( 9 | // defaultRemotePort specifies the default libvirtd port. 10 | defaultRemotePort = "16509" 11 | 12 | // defaultRemoteTimeout specifies the default libvirt dial timeout. 13 | defaultRemoteTimeout = 20 * time.Second 14 | ) 15 | 16 | // Remote implements connecting to a remote server's libvirt using tcp 17 | type Remote struct { 18 | timeout time.Duration 19 | host, port string 20 | } 21 | 22 | // RemoteOption is a function for setting remote dialer options. 23 | type RemoteOption func(*Remote) 24 | 25 | // WithRemoteTimeout sets the dial timeout. 26 | func WithRemoteTimeout(timeout time.Duration) RemoteOption { 27 | return func(r *Remote) { 28 | r.timeout = timeout 29 | } 30 | } 31 | 32 | // UsePort sets the port to dial for libirt on the target host server. 33 | func UsePort(port string) RemoteOption { 34 | return func(r *Remote) { 35 | r.port = port 36 | } 37 | } 38 | 39 | // NewRemote is a dialer for connecting to libvirt running on another server. 40 | func NewRemote(hostAddr string, opts ...RemoteOption) *Remote { 41 | r := &Remote{ 42 | timeout: defaultRemoteTimeout, 43 | host: hostAddr, 44 | port: defaultRemotePort, 45 | } 46 | 47 | for _, opt := range opts { 48 | opt(r) 49 | } 50 | 51 | return r 52 | } 53 | 54 | // Dial connects to libvirt running on another server. 55 | func (r *Remote) Dial() (net.Conn, error) { 56 | return net.DialTimeout( 57 | "tcp", 58 | net.JoinHostPort(r.host, r.port), 59 | r.timeout, 60 | ) 61 | } 62 | -------------------------------------------------------------------------------- /socket/dialers/tls.go: -------------------------------------------------------------------------------- 1 | package dialers 2 | 3 | import ( 4 | "crypto/tls" 5 | "crypto/x509" 6 | "errors" 7 | "fmt" 8 | "io/fs" 9 | "net" 10 | "os" 11 | "os/user" 12 | "path/filepath" 13 | "time" 14 | ) 15 | 16 | const ( 17 | // defaultTLSPort specifies the default libvirtd port. 18 | defaultTLSPort = "16514" 19 | 20 | // defaultTLSTimeout specifies the default libvirt dial timeout. 21 | defaultTLSTimeout = 20 * time.Second 22 | ) 23 | 24 | type certDirs struct { 25 | KeyPath string 26 | CertPath string 27 | } 28 | 29 | // TLS implements connecting to a remote server's libvirt using tls 30 | type TLS struct { 31 | timeout time.Duration 32 | host, port string 33 | insecureSkipVerify bool 34 | certSearchPaths []certDirs 35 | caSearchPaths []string 36 | } 37 | 38 | // TLSOption is a function for setting remote dialer options. 39 | type TLSOption func(*TLS) 40 | 41 | // WithInsecureNoVerify ignores the validity of the server certificate. 42 | func WithInsecureNoVerify() TLSOption { 43 | return func(r *TLS) { 44 | r.insecureSkipVerify = true 45 | } 46 | } 47 | 48 | // UseTLSPort sets the port to dial for libirt on the target host server. 49 | func UseTLSPort(port string) TLSOption { 50 | return func(r *TLS) { 51 | r.port = port 52 | } 53 | } 54 | 55 | // UsePKIPath sets the search path for TLS certificate files. 56 | func UsePKIPath(pkiPath string) TLSOption { 57 | return func(r *TLS) { 58 | r.certSearchPaths = []certDirs{ 59 | { 60 | KeyPath: pkiPath, 61 | CertPath: pkiPath, 62 | }, 63 | } 64 | r.caSearchPaths = []string{pkiPath} 65 | } 66 | } 67 | 68 | // NewTLS is a dialer for connecting to libvirt running on another server. 69 | func NewTLS(hostAddr string, opts ...TLSOption) *TLS { 70 | r := &TLS{ 71 | timeout: defaultTLSTimeout, 72 | host: hostAddr, 73 | port: defaultTLSPort, 74 | certSearchPaths: []certDirs{ 75 | { 76 | KeyPath: "/etc/pki/libvirt/private/", 77 | CertPath: "/etc/pki/libvirt/", 78 | }, 79 | }, 80 | caSearchPaths: []string{"/etc/pki/CA/"}, 81 | } 82 | 83 | if u, err := user.Current(); err != nil || u.Uid != "0" { 84 | cd := filepath.Join(u.HomeDir, ".pki", "libvirt") 85 | r.certSearchPaths = append([]certDirs{{KeyPath: cd, CertPath: cd}}, r.certSearchPaths...) 86 | // Some libvirt docs erroneously state that the user location for the 87 | // CA cert is in ~/.pki/ but it is in fact in ~/.pki/libvirt/ 88 | r.caSearchPaths = append([]string{cd}, r.caSearchPaths...) 89 | } 90 | 91 | for _, opt := range opts { 92 | opt(r) 93 | } 94 | 95 | return r 96 | } 97 | 98 | func (r *TLS) clientCert() (*tls.Certificate, error) { 99 | var errs []error 100 | 101 | for _, dirs := range r.certSearchPaths { 102 | certFile, err := os.ReadFile(filepath.Join(dirs.CertPath, "clientcert.pem")) 103 | if err != nil { 104 | errs = append(errs, 105 | fmt.Errorf("could not read tls client cert: %w", err)) 106 | continue 107 | } 108 | 109 | keyFile, err := os.ReadFile(filepath.Join(dirs.KeyPath, "clientkey.pem")) 110 | if err != nil { 111 | errs = append(errs, 112 | fmt.Errorf("could not read tls private key: %w", err)) 113 | continue 114 | } 115 | 116 | cert, err := tls.X509KeyPair(certFile, keyFile) 117 | if err != nil { 118 | return nil, fmt.Errorf("invalid tls client cert: %w", err) 119 | } 120 | return &cert, nil 121 | } 122 | return nil, errors.Join(errs...) 123 | } 124 | 125 | func (r *TLS) caCerts(optional bool) (*x509.CertPool, error) { 126 | var errs []error 127 | pool := x509.NewCertPool() 128 | 129 | for _, dir := range r.caSearchPaths { 130 | if caFile, err := os.ReadFile(filepath.Join(dir, "cacert.pem")); err == nil { 131 | pool.AppendCertsFromPEM(caFile) 132 | return pool, nil 133 | } else if !(optional && errors.Is(err, fs.ErrNotExist)) { 134 | errs = append(errs, 135 | fmt.Errorf("could not read tls CA cert: %w", err)) 136 | } 137 | } 138 | return nil, errors.Join(errs...) 139 | } 140 | 141 | func (r *TLS) config() (*tls.Config, error) { 142 | cert, err := r.clientCert() 143 | if err != nil { 144 | return nil, err 145 | } 146 | rootCAs, err := r.caCerts(r.insecureSkipVerify) 147 | if err != nil { 148 | return nil, err 149 | } 150 | 151 | return &tls.Config{ 152 | Certificates: []tls.Certificate{*cert}, 153 | RootCAs: rootCAs, 154 | InsecureSkipVerify: r.insecureSkipVerify, //nolint:gosec 155 | }, nil 156 | } 157 | 158 | // Dial connects to libvirt running on another server. 159 | func (r *TLS) Dial() (net.Conn, error) { 160 | conf, err := r.config() 161 | if err != nil { 162 | return nil, err 163 | } 164 | 165 | netDialer := net.Dialer{ 166 | Timeout: r.timeout, 167 | } 168 | c, err := tls.DialWithDialer( 169 | &netDialer, 170 | "tcp", 171 | net.JoinHostPort(r.host, r.port), 172 | conf, 173 | ) 174 | if err != nil { 175 | return nil, err 176 | } 177 | 178 | // When running over TLS, after connection libvirt writes a single byte to 179 | // the socket to indicate whether the server's check of the client's 180 | // certificate has succeeded. 181 | // See https://github.com/digitalocean/go-libvirt/issues/89#issuecomment-1607300636 182 | // for more details. 183 | buf := make([]byte, 1) 184 | if n, err := c.Read(buf); err != nil { 185 | c.Close() 186 | return nil, err 187 | } else if n != 1 || buf[0] != byte(1) { 188 | c.Close() 189 | return nil, errors.New("server verification (of our certificate or IP address) failed") 190 | } 191 | 192 | return c, nil 193 | } 194 | -------------------------------------------------------------------------------- /socket/socket.go: -------------------------------------------------------------------------------- 1 | package socket 2 | 3 | import ( 4 | "bufio" 5 | "encoding/binary" 6 | "errors" 7 | "io" 8 | "net" 9 | "sync" 10 | "syscall" 11 | "time" 12 | "unsafe" 13 | 14 | "github.com/digitalocean/go-libvirt/internal/constants" 15 | ) 16 | 17 | const disconnectTimeout = 5 * time.Second 18 | 19 | // request and response statuses 20 | const ( 21 | // StatusOK is always set for method calls or events. 22 | // For replies it indicates successful completion of the method. 23 | // For streams it indicates confirmation of the end of file on the stream. 24 | StatusOK = iota 25 | 26 | // StatusError for replies indicates that the method call failed 27 | // and error information is being returned. For streams this indicates 28 | // that not all data was sent and the stream has aborted. 29 | StatusError 30 | 31 | // StatusContinue is only used for streams. 32 | // This indicates that further data packets will be following. 33 | StatusContinue 34 | ) 35 | 36 | // request and response types 37 | const ( 38 | // Call is used when making calls to the remote server. 39 | Call = iota 40 | 41 | // Reply indicates a server reply. 42 | Reply 43 | 44 | // Message is an asynchronous notification. 45 | Message 46 | 47 | // Stream represents a stream data packet. 48 | Stream 49 | 50 | // CallWithFDs is used by a client to indicate the request has 51 | // arguments with file descriptors. 52 | CallWithFDs 53 | 54 | // ReplyWithFDs is used by a server to indicate the request has 55 | // arguments with file descriptors. 56 | ReplyWithFDs 57 | ) 58 | 59 | // Dialer is an interface for connecting to libvirt's underlying socket. 60 | type Dialer interface { 61 | Dial() (net.Conn, error) 62 | } 63 | 64 | // Router is an interface used to route packets to the appropriate clients. 65 | type Router interface { 66 | Route(*Header, []byte) 67 | } 68 | 69 | // Socket represents a libvirt Socket and its connection state 70 | type Socket struct { 71 | dialer Dialer 72 | router Router 73 | 74 | conn net.Conn 75 | reader *bufio.Reader 76 | writer *bufio.Writer 77 | // used to serialize any Socket writes and any updates to conn, r, or w 78 | mu *sync.Mutex 79 | 80 | // disconnected is closed when the listen goroutine associated with a 81 | // Socket connection has returned. 82 | disconnected chan struct{} 83 | } 84 | 85 | // packet represents a RPC request or response. 86 | type packet struct { 87 | // Size of packet, in bytes, including length. 88 | // Len + Header + Payload 89 | Len uint32 90 | Header Header 91 | } 92 | 93 | // Global packet instance, for use with unsafe.Sizeof() 94 | var _p packet 95 | 96 | // Header is a libvirt rpc packet header 97 | type Header struct { 98 | // Program identifier 99 | Program uint32 100 | 101 | // Program version 102 | Version uint32 103 | 104 | // Remote procedure identifier 105 | Procedure uint32 106 | 107 | // Call type, e.g., Reply 108 | Type uint32 109 | 110 | // Call serial number 111 | Serial int32 112 | 113 | // Request status, e.g., StatusOK 114 | Status uint32 115 | } 116 | 117 | // New initializes a new type for managing the Socket. 118 | func New(dialer Dialer, router Router) *Socket { 119 | s := &Socket{ 120 | dialer: dialer, 121 | router: router, 122 | disconnected: make(chan struct{}), 123 | mu: &sync.Mutex{}, 124 | } 125 | 126 | // we start with a closed channel since that indicates no connection 127 | close(s.disconnected) 128 | 129 | return s 130 | } 131 | 132 | // Connect uses the dialer provided on creation to establish 133 | // underlying physical connection to the desired libvirt. 134 | func (s *Socket) Connect() error { 135 | s.mu.Lock() 136 | defer s.mu.Unlock() 137 | 138 | if !s.isDisconnected() { 139 | return errors.New("already connected to socket") 140 | } 141 | conn, err := s.dialer.Dial() 142 | if err != nil { 143 | return err 144 | } 145 | 146 | s.conn = conn 147 | s.reader = bufio.NewReader(conn) 148 | s.writer = bufio.NewWriter(conn) 149 | s.disconnected = make(chan struct{}) 150 | 151 | go s.listenAndRoute() 152 | 153 | return nil 154 | } 155 | 156 | // Disconnect closes the Socket connection to libvirt and waits for the reader 157 | // gorouting to shut down. 158 | func (s *Socket) Disconnect() error { 159 | // just return if we're already disconnected 160 | if s.isDisconnected() { 161 | return nil 162 | } 163 | 164 | err := s.conn.Close() 165 | if err != nil { 166 | return err 167 | } 168 | 169 | // now we wait for the reader to return so as not to avoid it nil 170 | // referencing 171 | // Put this in a select, 172 | // and have it only nil out the conn value if it doesn't fail 173 | select { 174 | case <-s.disconnected: 175 | case <-time.After(disconnectTimeout): 176 | return errors.New("timed out waiting for Disconnect cleanup") 177 | } 178 | 179 | return nil 180 | } 181 | 182 | // Disconnected returns a channel that will be closed once the current 183 | // connection is closed. This can happen due to an explicit call to Disconnect 184 | // from the client, or due to non-temporary Read or Write errors encountered. 185 | func (s *Socket) Disconnected() <-chan struct{} { 186 | return s.disconnected 187 | } 188 | 189 | // isDisconnected is a non-blocking function to query whether a connection 190 | // is disconnected or not. 191 | func (s *Socket) isDisconnected() bool { 192 | select { 193 | case <-s.disconnected: 194 | return true 195 | default: 196 | return false 197 | } 198 | } 199 | 200 | // listenAndRoute reads packets from the Socket and calls the provided 201 | // Router function to route them 202 | func (s *Socket) listenAndRoute() { 203 | // only returns once it detects a non-temporary error related to the 204 | // underlying connection 205 | listen(s.reader, s.router) 206 | 207 | // signal any clients listening that the connection has been disconnected 208 | close(s.disconnected) 209 | } 210 | 211 | // listen processes incoming data and routes 212 | // responses to their respective callback handler. 213 | func listen(s io.Reader, router Router) { 214 | for { 215 | // response packet length 216 | length, err := pktlen(s) 217 | if err != nil { 218 | if isTemporary(err) { 219 | continue 220 | } 221 | // connection is no longer valid, so shutdown 222 | return 223 | } 224 | 225 | // response header 226 | h, err := extractHeader(s) 227 | if err != nil { 228 | // invalid packet 229 | continue 230 | } 231 | 232 | // payload: packet length minus what was previously read 233 | size := int(length) - int(unsafe.Sizeof(_p)) 234 | buf := make([]byte, size) 235 | _, err = io.ReadFull(s, buf) 236 | if err != nil { 237 | // invalid packet 238 | continue 239 | } 240 | 241 | // route response to caller 242 | router.Route(h, buf) 243 | } 244 | } 245 | 246 | // isTemporary returns true if the error returned from a read is transient. 247 | // If the error type is an OpError, check whether the net connection 248 | // error condition is temporary (which means we can keep using the 249 | // connection). 250 | // Errors not of the net.OpError type tend to be things like io.EOF, 251 | // syscall.EINVAL, or io.ErrClosedPipe (i.e. all things that 252 | // indicate the connection in use is no longer valid.) 253 | func isTemporary(err error) bool { 254 | opErr, ok := err.(*net.OpError) 255 | if ok { 256 | return opErr.Temporary() 257 | } 258 | return false 259 | } 260 | 261 | // pktlen returns the length of an incoming RPC packet. Read errors will 262 | // result in a returned response length of 0 and a non-nil error. 263 | func pktlen(r io.Reader) (uint32, error) { 264 | buf := make([]byte, unsafe.Sizeof(_p.Len)) 265 | 266 | // extract the packet's length from the header 267 | _, err := io.ReadFull(r, buf) 268 | if err != nil { 269 | return 0, err 270 | } 271 | 272 | return binary.BigEndian.Uint32(buf), nil 273 | } 274 | 275 | // extractHeader returns the decoded header from an incoming response. 276 | func extractHeader(r io.Reader) (*Header, error) { 277 | buf := make([]byte, unsafe.Sizeof(_p.Header)) 278 | 279 | // extract the packet's header from r 280 | _, err := io.ReadFull(r, buf) 281 | if err != nil { 282 | return nil, err 283 | } 284 | 285 | return &Header{ 286 | Program: binary.BigEndian.Uint32(buf[0:4]), 287 | Version: binary.BigEndian.Uint32(buf[4:8]), 288 | Procedure: binary.BigEndian.Uint32(buf[8:12]), 289 | Type: binary.BigEndian.Uint32(buf[12:16]), 290 | Serial: int32(binary.BigEndian.Uint32(buf[16:20])), 291 | Status: binary.BigEndian.Uint32(buf[20:24]), 292 | }, nil 293 | } 294 | 295 | // SendPacket sends a packet to libvirt on the socket connection. 296 | func (s *Socket) SendPacket( 297 | serial int32, 298 | proc uint32, 299 | program uint32, 300 | payload []byte, 301 | typ uint32, 302 | status uint32, 303 | ) error { 304 | p := packet{ 305 | Header: Header{ 306 | Program: program, 307 | Version: constants.ProtocolVersion, 308 | Procedure: proc, 309 | Type: typ, 310 | Serial: serial, 311 | Status: status, 312 | }, 313 | } 314 | 315 | size := int(unsafe.Sizeof(p.Len)) + int(unsafe.Sizeof(p.Header)) 316 | if payload != nil { 317 | size += len(payload) 318 | } 319 | p.Len = uint32(size) 320 | 321 | if s.isDisconnected() { 322 | // this mirrors what a lot of net code return on use of a no 323 | // longer valid connection 324 | return syscall.EINVAL 325 | } 326 | 327 | s.mu.Lock() 328 | defer s.mu.Unlock() 329 | 330 | err := binary.Write(s.writer, binary.BigEndian, p) 331 | if err != nil { 332 | return err 333 | } 334 | 335 | // write payload 336 | if payload != nil { 337 | err = binary.Write(s.writer, binary.BigEndian, payload) 338 | if err != nil { 339 | return err 340 | } 341 | } 342 | 343 | return s.writer.Flush() 344 | } 345 | 346 | // SendStream sends a stream of packets to libvirt on the socket connection. 347 | func (s *Socket) SendStream(serial int32, proc uint32, program uint32, 348 | stream io.Reader, abort chan bool) error { 349 | // Keep total packet length under 4 MiB to follow possible limitation in libvirt server code 350 | buf := make([]byte, 4*MiB-unsafe.Sizeof(_p)) 351 | for { 352 | select { 353 | case <-abort: 354 | return s.SendPacket(serial, proc, program, nil, Stream, StatusError) 355 | default: 356 | } 357 | n, err := stream.Read(buf) 358 | if n > 0 { 359 | err2 := s.SendPacket(serial, proc, program, buf[:n], Stream, StatusContinue) 360 | if err2 != nil { 361 | return err2 362 | } 363 | } 364 | if err != nil { 365 | if err == io.EOF { 366 | return s.SendPacket(serial, proc, program, nil, Stream, StatusOK) 367 | } 368 | // keep original error 369 | err2 := s.SendPacket(serial, proc, program, nil, Stream, StatusError) 370 | if err2 != nil { 371 | return err2 372 | } 373 | return err 374 | } 375 | } 376 | } 377 | -------------------------------------------------------------------------------- /socket/socket_test.go: -------------------------------------------------------------------------------- 1 | package socket 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/digitalocean/go-libvirt/internal/constants" 8 | ) 9 | 10 | var testHeader = []byte{ 11 | 0x20, 0x00, 0x80, 0x86, // program 12 | 0x00, 0x00, 0x00, 0x01, // version 13 | 0x00, 0x00, 0x00, 0x01, // procedure 14 | 0x00, 0x00, 0x00, 0x00, // type 15 | 0x00, 0x00, 0x00, 0x00, // serial 16 | 0x00, 0x00, 0x00, 0x00, // status 17 | } 18 | 19 | func TestPktLen(t *testing.T) { 20 | data := []byte{0x00, 0x00, 0x00, 0xa} // uint32:10 21 | r := bytes.NewBuffer(data) 22 | 23 | expected := uint32(10) 24 | actual, err := pktlen(r) 25 | if err != nil { 26 | t.Error(err) 27 | } 28 | 29 | if expected != actual { 30 | t.Errorf("expected packet length %q, got %q", expected, actual) 31 | } 32 | } 33 | 34 | func TestExtractHeader(t *testing.T) { 35 | r := bytes.NewBuffer(testHeader) 36 | h, err := extractHeader(r) 37 | if err != nil { 38 | t.Error(err) 39 | } 40 | 41 | if h.Program != constants.Program { 42 | t.Errorf("expected Program %q, got %q", constants.Program, h.Program) 43 | } 44 | 45 | if h.Version != constants.ProtocolVersion { 46 | t.Errorf("expected version %q, got %q", constants.ProtocolVersion, h.Version) 47 | } 48 | 49 | if h.Procedure != constants.ProcConnectOpen { 50 | t.Errorf("expected procedure %q, got %q", constants.ProcConnectOpen, h.Procedure) 51 | } 52 | 53 | if h.Type != Call { 54 | t.Errorf("expected type %q, got %q", Call, h.Type) 55 | } 56 | 57 | if h.Status != StatusOK { 58 | t.Errorf("expected status %q, got %q", StatusOK, h.Status) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /socket/units.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The go-libvirt Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // This module provides different units of measurement to make other 16 | // code more readable. 17 | 18 | package socket 19 | 20 | const ( 21 | // B - byte 22 | B = 1 23 | // KiB - kibibyte 24 | KiB = 1024 * B 25 | // MiB - mebibyte 26 | MiB = 1024 * KiB 27 | ) 28 | -------------------------------------------------------------------------------- /testdata/test-domain.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | test 4 | 1024 5 | 1024 6 | afc2ef71-66e0-45a7-a5ec-d8ba1ea8177d 7 | 8 | hvm 9 | 10 | 11 | 12 | 13 | 14 | 15 | destroy 16 | restart 17 | restart 18 | 1 19 | 20 | /usr/bin/qemu-system-x86_64 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /testdata/test-pool.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | test 4 | 5 | /tmp 6 | 7 | 8 | -------------------------------------------------------------------------------- /testdata/test-secret.xml: -------------------------------------------------------------------------------- 1 | 2 | test 3 | 19fdc2f2-fa64-46f3-bacf-42a8aafca6dd 4 | 5 | /tmp 6 | 7 | 8 | --------------------------------------------------------------------------------