Full Code of pojntfx/go-nbd for AI

main 2d78be2564f3 cached
22 files
51.4 KB
14.5k tokens
78 symbols
1 requests
Download .txt
Repository: pojntfx/go-nbd
Branch: main
Commit: 2d78be2564f3
Files: 22
Total size: 51.4 KB

Directory structure:
gitextract_lo09498w/

├── .github/
│   └── workflows/
│       └── hydrun.yaml
├── .gitignore
├── Hydrunfile
├── LICENSE
├── Makefile
├── README.md
├── cmd/
│   ├── go-nbd-example-client/
│   │   └── main.go
│   ├── go-nbd-example-server-file/
│   │   └── main.go
│   └── go-nbd-example-server-memory/
│       └── main.go
├── go.mod
├── go.sum
└── pkg/
    ├── backend/
    │   ├── backend.go
    │   ├── file.go
    │   └── memory.go
    ├── client/
    │   └── nbd.go
    ├── ioctl/
    │   ├── negotiation_cgo.go
    │   ├── negotiation_go_amd64.go
    │   ├── transmission_cgo.go
    │   └── transmission_go_amd64.go
    ├── protocol/
    │   ├── negotiation.go
    │   └── transmission.go
    └── server/
        └── nbd.go

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/hydrun.yaml
================================================
name: hydrun CI

on:
  push:
  pull_request:
  schedule:
    - cron: "0 0 * * 0"

jobs:
  build-linux:
    runs-on: ${{ matrix.target.runner }}
    permissions:
      contents: read
    strategy:
      matrix:
        target:
          # Tests
          - id: test
            src: .
            os: golang:bookworm
            flags: -e '-v /tmp/ccache:/root/.cache/go-build'
            cmd: GOFLAGS="-short" ./Hydrunfile test
            dst: out/nonexistent
            runner: ubuntu-latest

          # We don't vendor the binaries

    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - name: Restore ccache
        uses: actions/cache/restore@v4
        with:
          path: |
            /tmp/ccache
          key: cache-ccache-${{ matrix.target.id }}
      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
      - name: Set up hydrun
        run: |
          curl -L -o /tmp/hydrun "https://github.com/pojntfx/hydrun/releases/latest/download/hydrun.linux-$(uname -m)"
          sudo install /tmp/hydrun /usr/local/bin
      - name: Build with hydrun
        working-directory: ${{ matrix.target.src }}
        run: hydrun -o ${{ matrix.target.os }} ${{ matrix.target.flags }} "${{ matrix.target.cmd }}"
      - name: Fix permissions for output
        run: sudo chown -R $USER .
      - name: Save ccache
        uses: actions/cache/save@v4
        with:
          path: |
            /tmp/ccache
          key: cache-ccache-${{ matrix.target.id }}
      - name: Upload output
        uses: actions/upload-artifact@v4
        with:
          name: ${{ matrix.target.id }}
          path: ${{ matrix.target.dst }}

  publish-linux:
    runs-on: ubuntu-latest
    permissions:
      contents: write
    needs: build-linux

    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - name: Download output
        uses: actions/download-artifact@v4
        with:
          path: /tmp/out
      - name: Extract branch name
        id: extract_branch
        run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})"
      - name: Publish pre-release to GitHub releases
        if: ${{ github.ref == 'refs/heads/main' }}
        uses: softprops/action-gh-release@v2
        with:
          tag_name: release-${{ steps.extract_branch.outputs.branch }}
          prerelease: true
          files: |
            /tmp/out/*/*
      - name: Publish release to GitHub releases
        if: startsWith(github.ref, 'refs/tags/v')
        uses: softprops/action-gh-release@v2
        with:
          prerelease: false
          files: |
            /tmp/out/*/*


================================================
FILE: .gitignore
================================================
out


================================================
FILE: Hydrunfile
================================================
#!/bin/bash

set -e

# Test
if [ "$1" = "test" ]; then
  # Configure Git
  git config --global --add safe.directory '*'

  # Generate dependencies
  make depend

  # Run tests
  make test

  exit 0
fi


================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/

TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

1. Definitions.

"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.

"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.

"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.

"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.

"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.

"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.

"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).

"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.

"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."

"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.

2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.

3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.

4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:

     (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and

     (b) You must cause any modified files to carry prominent notices stating that You changed the files; and

     (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and

     (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.

     You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.

5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.

6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.

7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.

8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.

9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.

END OF TERMS AND CONDITIONS

APPENDIX: How to apply the Apache License to your work.

To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!)  The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives.

Copyright [yyyy] [name of copyright owner]

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.


================================================
FILE: Makefile
================================================
# Public variables
DESTDIR ?=
PREFIX ?= /usr/local
OUTPUT_DIR ?= out
DST ?=

# Private variables
obj = go-nbd-example-client go-nbd-example-server-file go-nbd-example-server-memory
all: $(addprefix build/,$(obj))

# Build
build: $(addprefix build/,$(obj))
$(addprefix build/,$(obj)):
ifdef DST
	go build -o $(DST) ./cmd/$(subst build/,,$@)
else
	go build -o $(OUTPUT_DIR)/$(subst build/,,$@) ./cmd/$(subst build/,,$@)
endif

# Install
install: $(addprefix install/,$(obj))
$(addprefix install/,$(obj)):
	install -D -m 0755 $(OUTPUT_DIR)/$(subst install/,,$@) $(DESTDIR)$(PREFIX)/bin/$(subst install/,,$@)

# Uninstall
uninstall: $(addprefix uninstall/,$(obj))
$(addprefix uninstall/,$(obj)):
	rm $(DESTDIR)$(PREFIX)/bin/$(subst uninstall/,,$@)

# Run
$(addprefix run/,$(obj)):
	$(subst run/,,$@) $(ARGS)

# Test
test:
	go test -timeout 3600s -parallel $(shell nproc) ./...

# Benchmark
benchmark:
	go test -timeout 3600s -bench=./... ./...

# Clean
clean:
	rm -rf out

# Dependencies
depend:
	true

================================================
FILE: README.md
================================================
<img alt="Project icon" style="vertical-align: middle;" src="./docs/icon.svg" width="128" height="128" align="left">

# go-nbd

Pure Go NBD server and client library.

<br/>

[![hydrun CI](https://github.com/pojntfx/go-nbd/actions/workflows/hydrun.yaml/badge.svg)](https://github.com/pojntfx/go-nbd/actions/workflows/hydrun.yaml)
![Go Version](https://img.shields.io/badge/go%20version-%3E=1.20-61CFDD.svg)
[![Go Reference](https://pkg.go.dev/badge/github.com/pojntfx/go-nbd.svg)](https://pkg.go.dev/github.com/pojntfx/go-nbd)
[![Matrix](https://img.shields.io/matrix/go-nbd:matrix.org)](https://matrix.to/#/#go-nbd:matrix.org?via=matrix.org)

## Overview

go-nbd is a lean NBD server and client library supporting the baseline protocol.

It enables you to:

- **Build NBD servers and clients in Go:** Develop Network Block Device servers and clients using the efficient and easy-to-understand Go programming language, without having to fallback to CGo.
- **Expose any `io.ReadWriter` as a block device:** Effortlessly turn a file, byte slice, S3 bucket or other `io.ReadWriter` into a fully-fledged block device.
- **Bridge with legacy services:** If you need to make your application's dynamic data available to a legacy system, providing a NBD interface can be the perfect solution.

## Installation

You can add go-nbd to your Go project by running the following:

```shell
$ go get github.com/pojntfx/go-nbd/...@latest
```

## Tutorial

> TL;DR: Define a backend, expose it with a server, connect a block device with the client and setup/mount the filesystem.

### 1. Define a Backend

First, define a backend; it should conform to this simple interface:

```go
type Backend interface {
	ReadAt(p []byte, off int64) (n int, err error)
	WriteAt(p []byte, off int64) (n int, err error)
	Size() (int64, error)
	Sync() error
}
```

A simple file-based backend could look like this:

```go
// server/main.go

type FileBackend struct {
	file *os.File
	lock sync.RWMutex
}

func NewFileBackend(file *os.File) *FileBackend {
	return &FileBackend{file, sync.RWMutex{}}
}

func (b *FileBackend) ReadAt(p []byte, off int64) (n int, err error) {
	b.lock.RLock()

	n, err = b.file.ReadAt(p, off)

	b.lock.RUnlock()

	return
}

func (b *FileBackend) WriteAt(p []byte, off int64) (n int, err error) {
	b.lock.Lock()

	n, err = b.file.WriteAt(p, off)

	b.lock.Unlock()

	return
}

func (b *FileBackend) Size() (int64, error) {
	stat, err := b.file.Stat()
	if err != nil {
		return -1, err
	}

	return stat.Size(), nil
}

func (b *FileBackend) Sync() error {
	return b.file.Sync()
}
```

See [pkg/backend](./pkg/backend) for more backend examples.

### 2. Expose the Backend With a Server

Next, create the backend and expose it with a server:

```go
// server/main.go

b := NewFileBackend(f)

for {
	conn, err := l.Accept()
	if err != nil {
		continue
	}

	go func() {
		if err := server.Handle(
			conn,
			[]server.Export{
				{
					Name:        *name,
					Description: *description,
					Backend:     b,
				},
			},
			&server.Options{
				ReadOnly:           *readOnly,
				MinimumBlockSize:   uint32(*minimumBlockSize),
				PreferredBlockSize: uint32(*preferredBlockSize),
				MaximumBlockSize:   uint32(*maximumBlockSize),
			}); err != nil {
			panic(err)
		}
	}()
}
```

See [cmd/go-nbd-example-server-file/main.go](./cmd/go-nbd-example-server-file/main.go) for the full example.

### 3. Connect to the Server with a Client

In a new `main` package, connect to the server by creating a client; note that you'll have to `modprobe nbd` and run the command as `root`:

```go
// client/main.go

if err := client.Connect(conn, f, &client.Options{
	ExportName: *name,
	BlockSize:  uint32(*blockSize),
}); err != nil {
	panic(err)
}
```

See [cmd/go-nbd-example-client/main.go](./cmd/go-nbd-example-client/main.go) for the full example.

### 4. Setup and Mount the Filesystem

Lastly, create a filesystem on the block device and mount it:

```shell
$ sudo mkfs.ext4 /dev/nbd0
$ sudo mkdir -p /mnt
$ sudo mount -t ext4 /dev/nbd0 /mnt
```

You should now be able to use the mounted filesystem by navigating to `/mnt`.

🚀 That's it! We can't wait to see what you're going to build with go-nbd.

## Examples

To make getting started with go-nbd easier, take a look at the following examples:

- [NBD File Server](./cmd/go-nbd-example-server-file/main.go)
- [NBD Memory Server](./cmd/go-nbd-example-server-memory/main.go)
- [NBD Client](./cmd/go-nbd-example-client/main.go)

## Acknowledgements

- [abligh/gonbdserver](https://github.com/abligh/gonbdserver/) provided the initial inspiration for this project.
- [NetworkBlockDevice/nbd](https://github.com/NetworkBlockDevice/nbd/blob/master/doc/proto.md) provided the NBD protocol documentation.

## Contributing

To contribute, please use the [GitHub flow](https://guides.github.com/introduction/flow/) and follow our [Code of Conduct](./CODE_OF_CONDUCT.md).

To build and start a development version of one of the examples locally, run the following:

```shell
$ git clone https://github.com/pojntfx/go-nbd.git
$ cd go-nbd
$ mkdir -p out && rm -f out/disk.img && truncate -s 10G out/disk.img && go run ./cmd/go-nbd-example-server-file --file out/disk.img
$ go run ./cmd/go-nbd-example-server-memory

# With the C NBD client
$ sudo umount ~/Downloads/mnt; sudo nbd-client -d /dev/nbd1 && echo 'NBD starting' | sudo tee /dev/kmsg && sudo nbd-client -N default localhost 10809 /dev/nbd1

# With the Go NBD client
$ sudo umount ~/Downloads/mnt; go build -o /tmp/go-nbd-example-client ./cmd/go-nbd-example-client/ && sudo /tmp/go-nbd-example-client --file /dev/nbd1

$ sudo mkfs.ext4 /dev/nbd1
$ sync -f ~/Downloads/mnt; sudo umount ~/Downloads/mnt; sudo rm -rf ~/Downloads/mnt && sudo mkdir -p ~/Downloads/mnt && sudo mount -t ext4 /dev/nbd1 ~/Downloads/mnt && sudo chown -R "${USER}" ~/Downloads/mnt
```

Have any questions or need help? Chat with us [on Matrix](https://matrix.to/#/#go-nbd:matrix.org?via=matrix.org)!

## License

go-nbd (c) 2024 Felicitas Pojtinger and contributors

SPDX-License-Identifier: Apache-2.0


================================================
FILE: cmd/go-nbd-example-client/main.go
================================================
package main

import (
	"encoding/json"
	"flag"
	"log"
	"net"
	"os"
	"os/signal"

	"github.com/pojntfx/go-nbd/pkg/client"
)

func main() {
	file := flag.String("file", "/dev/nbd0", "Path to device file to create")
	raddr := flag.String("raddr", "127.0.0.1:10809", "Remote address")
	network := flag.String("network", "tcp", "Remote network (e.g. `tcp` or `unix`)")
	name := flag.String("name", "default", "Export name")
	list := flag.Bool("list", false, "List the exports and exit")
	blockSize := flag.Uint("block-size", 0, "Block size to use; 0 uses the server's preferred block size")

	flag.Parse()

	conn, err := net.Dial(*network, *raddr)
	if err != nil {
		panic(err)
	}
	defer conn.Close()

	log.Println("Connected to", conn.RemoteAddr())

	if *list {
		exports, err := client.List(conn)
		if err != nil {
			panic(err)
		}

		if err := json.NewEncoder(os.Stdout).Encode(exports); err != nil {
			panic(err)
		}

		return
	}

	f, err := os.Open(*file)
	if err != nil {
		panic(err)
	}
	defer f.Close()

	sigCh := make(chan os.Signal, 1)
	signal.Notify(sigCh, os.Interrupt)

	go func() {
		for range sigCh {
			if err := client.Disconnect(f); err != nil {
				panic(err)
			}

			os.Exit(0)
		}
	}()

	if err := client.Connect(conn, f, &client.Options{
		ExportName: *name,
		BlockSize:  uint32(*blockSize),
	}); err != nil {
		panic(err)
	}
}


================================================
FILE: cmd/go-nbd-example-server-file/main.go
================================================
package main

import (
	"flag"
	"log"
	"net"
	"os"

	"github.com/pojntfx/go-nbd/pkg/backend"
	"github.com/pojntfx/go-nbd/pkg/client"
	"github.com/pojntfx/go-nbd/pkg/server"
)

func main() {
	file := flag.String("file", "disk.img", "Path to file to expose")
	laddr := flag.String("laddr", ":10809", "Listen address")
	network := flag.String("network", "tcp", "Listen network (e.g. `tcp` or `unix`)")
	name := flag.String("name", "default", "Export name")
	description := flag.String("description", "The default export", "Export description")
	readOnly := flag.Bool("read-only", false, "Whether the export should be read-only")
	minimumBlockSize := flag.Uint("minimum-block-size", 1, "Minimum block size")
	preferredBlockSize := flag.Uint("preferred-block-size", client.MaximumBlockSize, "Preferred block size")
	maximumBlockSize := flag.Uint("maximum-block-size", 0xffffffff, "Maximum block size")
	multiConn := flag.Bool("multi-conn", true, "Whether to advertise support for multiple simultaneous connections")

	flag.Parse()

	l, err := net.Listen(*network, *laddr)
	if err != nil {
		panic(err)
	}
	defer l.Close()

	log.Println("Listening on", l.Addr())

	var f *os.File
	if *readOnly {
		f, err = os.OpenFile(*file, os.O_RDONLY, 0644)
		if err != nil {
			panic(err)
		}
	} else {
		f, err = os.OpenFile(*file, os.O_RDWR, 0644)
		if err != nil {
			panic(err)
		}
	}
	defer f.Close()

	b := backend.NewFileBackend(f)

	clients := 0
	for {
		conn, err := l.Accept()
		if err != nil {
			log.Println("Could not accept connection, continuing:", err)

			continue
		}

		clients++

		log.Printf("%v clients connected", clients)

		go func() {
			defer func() {
				_ = conn.Close()

				clients--

				if err := recover(); err != nil {
					log.Printf("Client disconnected with error: %v", err)
				}

				log.Printf("%v clients connected", clients)
			}()

			if err := server.Handle(
				conn,
				[]*server.Export{
					{
						Name:        *name,
						Description: *description,
						Backend:     b,
					},
				},
				&server.Options{
					ReadOnly:           *readOnly,
					MinimumBlockSize:   uint32(*minimumBlockSize),
					PreferredBlockSize: uint32(*preferredBlockSize),
					MaximumBlockSize:   uint32(*maximumBlockSize),
					SupportsMultiConn:  *multiConn,
				}); err != nil {
				panic(err)
			}
		}()
	}
}


================================================
FILE: cmd/go-nbd-example-server-memory/main.go
================================================
package main

import (
	"flag"
	"log"
	"net"

	"github.com/pojntfx/go-nbd/pkg/backend"
	"github.com/pojntfx/go-nbd/pkg/client"
	"github.com/pojntfx/go-nbd/pkg/server"
)

func main() {
	size := flag.Int64("size", 1073741824, "Size of the memory region to expose")
	laddr := flag.String("laddr", ":10809", "Listen address")
	network := flag.String("network", "tcp", "Listen network (e.g. `tcp` or `unix`)")
	name := flag.String("name", "default", "Export name")
	description := flag.String("description", "The default export", "Export description")
	readOnly := flag.Bool("read-only", false, "Whether the export should be read-only")
	minimumBlockSize := flag.Uint("minimum-block-size", 1, "Minimum block size")
	preferredBlockSize := flag.Uint("preferred-block-size", client.MaximumBlockSize, "Preferred block size")
	maximumBlockSize := flag.Uint("maximum-block-size", 0xffffffff, "Maximum block size")
	multiConn := flag.Bool("multi-conn", true, "Whether to advertise support for multiple simultaneous connections")

	flag.Parse()

	l, err := net.Listen(*network, *laddr)
	if err != nil {
		panic(err)
	}
	defer l.Close()

	log.Println("Listening on", l.Addr())

	b := backend.NewMemoryBackend(make([]byte, *size))

	clients := 0
	for {
		conn, err := l.Accept()
		if err != nil {
			log.Println("Could not accept connection, continuing:", err)

			continue
		}

		clients++

		log.Printf("%v clients connected", clients)

		go func() {
			defer func() {
				_ = conn.Close()

				clients--

				if err := recover(); err != nil {
					log.Printf("Client disconnected with error: %v", err)
				}

				log.Printf("%v clients connected", clients)
			}()

			if err := server.Handle(
				conn,
				[]*server.Export{
					{
						Name:        *name,
						Description: *description,
						Backend:     b,
					},
				},
				&server.Options{
					ReadOnly:           *readOnly,
					MinimumBlockSize:   uint32(*minimumBlockSize),
					PreferredBlockSize: uint32(*preferredBlockSize),
					MaximumBlockSize:   uint32(*maximumBlockSize),
					SupportsMultiConn:  *multiConn,
				}); err != nil {
				panic(err)
			}
		}()
	}
}


================================================
FILE: go.mod
================================================
module github.com/pojntfx/go-nbd

go 1.20

require github.com/pilebones/go-udev v0.9.0


================================================
FILE: go.sum
================================================
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/pilebones/go-udev v0.9.0 h1:N1uEO/SxUwtIctc0WLU0t69JeBxIYEYnj8lT/Nabl9Q=
github.com/pilebones/go-udev v0.9.0/go.mod h1:T2eI2tUSK0hA2WS5QLjXJUfQkluZQu+18Cqvem3CaXI=


================================================
FILE: pkg/backend/backend.go
================================================
package backend

import "io"

type Backend interface {
	io.ReaderAt
	io.WriterAt

	Size() (int64, error)
	Sync() error
}


================================================
FILE: pkg/backend/file.go
================================================
package backend

import (
	"io"
	"os"
	"sync"
)

type FileBackend struct {
	file *os.File
	lock sync.RWMutex
}

func NewFileBackend(file *os.File) *FileBackend {
	return &FileBackend{file, sync.RWMutex{}}
}

func (b *FileBackend) ReadAt(p []byte, off int64) (n int, err error) {
	b.lock.RLock()

	n, err = b.file.ReadAt(p, off)

	b.lock.RUnlock()

	return
}

func (b *FileBackend) WriteAt(p []byte, off int64) (n int, err error) {
	b.lock.Lock()

	n, err = b.file.WriteAt(p, off)

	b.lock.Unlock()

	return
}

func (b *FileBackend) Size() (int64, error) {
	size, err := b.file.Seek(0, io.SeekEnd)
	if err != nil {
		return -1, err
	}

	return size, nil
}

func (b *FileBackend) Sync() error {
	return b.file.Sync()
}


================================================
FILE: pkg/backend/memory.go
================================================
package backend

import (
	"io"
	"sync"
)

type MemoryBackend struct {
	memory []byte
	lock   sync.Mutex
}

func NewMemoryBackend(memory []byte) *MemoryBackend {
	return &MemoryBackend{memory, sync.Mutex{}}
}

func (b *MemoryBackend) ReadAt(p []byte, off int64) (n int, err error) {
	b.lock.Lock()

	if off >= int64(len(b.memory)) {
		return 0, io.EOF
	}

	n = copy(p, b.memory[off:off+int64(len(p))])

	b.lock.Unlock()

	return
}

func (b *MemoryBackend) WriteAt(p []byte, off int64) (n int, err error) {
	b.lock.Lock()

	if off >= int64(len(b.memory)) {
		return 0, io.EOF
	}

	n = copy(b.memory[off:off+int64(len(p))], p)

	if n < len(p) {
		return n, io.ErrShortWrite
	}

	b.lock.Unlock()

	return
}

func (b *MemoryBackend) Size() (int64, error) {
	return int64(len(b.memory)), nil
}

func (b *MemoryBackend) Sync() error {
	return nil
}


================================================
FILE: pkg/client/nbd.go
================================================
package client

import (
	"bytes"
	"encoding/binary"
	"errors"
	"io"
	"net"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"syscall"
	"time"

	"github.com/pilebones/go-udev/netlink"
	"github.com/pojntfx/go-nbd/pkg/ioctl"
	"github.com/pojntfx/go-nbd/pkg/protocol"
	"github.com/pojntfx/go-nbd/pkg/server"
)

const (
	MinimumBlockSize = 512  // This is the minimum value that works in practice, else the client stops with "invalid argument"
	MaximumBlockSize = 4096 // This is the maximum value that works in practice, else the client stops with "invalid argument"
)

var (
	ErrUnsupportedNetwork         = errors.New("unsupported network")
	ErrUnknownReply               = errors.New("unknown reply")
	ErrUnknownInfo                = errors.New("unknown info")
	ErrUnknownErr                 = errors.New("unknown error")
	ErrUnsupportedServerBlockSize = errors.New("server proposed unsupported block size")
	ErrMinimumBlockSize           = errors.New("block size below mimimum requested")
	ErrMaximumBlockSize           = errors.New("block size above maximum requested")
	ErrBlockSizeNotPowerOfTwo     = errors.New("block size is not a power of 2")
)

type Options struct {
	ExportName             string
	BlockSize              uint32
	OnConnected            func()
	ReadyCheckUdev         bool
	ReadyCheckPollInterval time.Duration
	Timeout                int
}

func negotiateNewstyle(conn net.Conn) error {
	var newstyleHeader protocol.NegotiationNewstyleHeader
	if err := binary.Read(conn, binary.BigEndian, &newstyleHeader); err != nil {
		return err
	}

	if newstyleHeader.OldstyleMagic != protocol.NEGOTIATION_MAGIC_OLDSTYLE {
		return server.ErrInvalidMagic
	}

	if newstyleHeader.OptionMagic != protocol.NEGOTIATION_MAGIC_OPTION {
		return server.ErrInvalidMagic
	}

	if _, err := conn.Write(make([]byte, 4)); err != nil { // Send client flags (uint32)
		return err
	}

	return nil
}

func Connect(conn net.Conn, device *os.File, options *Options) error {
	if options == nil {
		options = &Options{}
	}

	if options.ExportName == "" {
		options.ExportName = "default"
	}

	if !options.ReadyCheckUdev && options.ReadyCheckPollInterval <= 0 {
		options.ReadyCheckPollInterval = time.Millisecond
	}

	var cfd uintptr
	switch c := conn.(type) {
	case *net.TCPConn:
		file, err := c.File()
		if err != nil {
			return err
		}

		cfd = uintptr(file.Fd())
	case *net.UnixConn:
		file, err := c.File()
		if err != nil {
			return err
		}

		cfd = uintptr(file.Fd())
	default:
		return ErrUnsupportedNetwork
	}

	fatal := make(chan error)
	if options.OnConnected != nil {
		if options.ReadyCheckUdev {
			udevConn := new(netlink.UEventConn)
			if err := udevConn.Connect(netlink.UdevEvent); err != nil {
				return err
			}
			defer udevConn.Close()

			var (
				udevReadyCh = make(chan netlink.UEvent)
				udevErrCh   = make(chan error)
				udevQuit    = udevConn.Monitor(udevReadyCh, udevErrCh, &netlink.RuleDefinitions{
					Rules: []netlink.RuleDefinition{
						{
							Env: map[string]string{
								"DEVNAME": device.Name(),
							},
						},
					},
				})
			)
			defer close(udevQuit)

			go func() {
				select {
				case <-udevReadyCh:
					close(udevQuit)

					options.OnConnected()

					return
				case err := <-udevErrCh:
					fatal <- err

					return
				}
			}()
		} else {
			go func() {
				sizeFile, err := os.Open(filepath.Join("/sys", "block", filepath.Base(device.Name()), "size"))
				if err != nil {
					fatal <- err

					return
				}
				defer sizeFile.Close()

				for {
					if _, err := sizeFile.Seek(0, io.SeekStart); err != nil {
						fatal <- err

						return
					}

					rsize, err := io.ReadAll(sizeFile)
					if err != nil {
						fatal <- err

						return
					}

					size, err := strconv.ParseInt(strings.TrimSpace(string(rsize)), 10, 64)
					if err != nil {
						fatal <- err

						return
					}

					if size > 0 {
						options.OnConnected()

						return
					}

					time.Sleep(options.ReadyCheckPollInterval)
				}
			}()
		}
	}

	if _, _, err := syscall.Syscall(
		syscall.SYS_IOCTL,
		device.Fd(),
		ioctl.NEGOTIATION_IOCTL_SET_SOCK,
		uintptr(cfd),
	); err != 0 {
		return err
	}

	if err := negotiateNewstyle(conn); err != nil {
		return err
	}

	if err := binary.Write(conn, binary.BigEndian, protocol.NegotiationOptionHeader{
		OptionMagic: protocol.NEGOTIATION_MAGIC_OPTION,
		ID:          protocol.NEGOTIATION_ID_OPTION_GO,
		Length:      0,
	}); err != nil {
		return err
	}

	exportName := []byte(options.ExportName)

	if err := binary.Write(conn, binary.BigEndian, uint32(len(exportName))); err != nil {
		return err
	}

	if _, err := conn.Write([]byte(exportName)); err != nil {
		return err
	}

	if err := binary.Write(conn, binary.BigEndian, uint16(0)); err != nil { // Send information request count (uint16)
		return err
	}

	size := uint64(0)
	chosenBlockSize := uint32(1)

n:
	for {
		var replyHeader protocol.NegotiationReplyHeader
		if err := binary.Read(conn, binary.BigEndian, &replyHeader); err != nil {
			return err
		}

		if replyHeader.ReplyMagic != protocol.NEGOTIATION_MAGIC_REPLY {
			return server.ErrInvalidMagic
		}

		switch replyHeader.Type {
		case protocol.NEGOTIATION_TYPE_REPLY_INFO:
			infoRaw := make([]byte, replyHeader.Length)
			if _, err := io.ReadFull(conn, infoRaw); err != nil {
				return err
			}

			var infoType uint16
			if err := binary.Read(bytes.NewBuffer(infoRaw), binary.BigEndian, &infoType); err != nil {
				return err
			}

			switch infoType {
			case protocol.NEGOTIATION_TYPE_INFO_EXPORT:
				var info protocol.NegotiationReplyInfo
				if err := binary.Read(bytes.NewBuffer(infoRaw), binary.BigEndian, &info); err != nil {
					return err
				}

				size = info.Size
			case protocol.NEGOTIATION_TYPE_INFO_NAME:
				// Discard export name
			case protocol.NEGOTIATION_TYPE_INFO_DESCRIPTION:
				// Discard export description
			case protocol.NEGOTIATION_TYPE_INFO_BLOCKSIZE:
				var info protocol.NegotiationReplyBlockSize
				if err := binary.Read(bytes.NewBuffer(infoRaw), binary.BigEndian, &info); err != nil {
					return err
				}

				if options.BlockSize == 0 {
					chosenBlockSize = info.PreferredBlockSize
				} else if options.BlockSize >= info.MinimumBlockSize && options.BlockSize <= info.MaximumBlockSize {
					chosenBlockSize = options.BlockSize
				} else {
					return ErrUnsupportedServerBlockSize
				}

				if chosenBlockSize > MaximumBlockSize {
					return ErrMaximumBlockSize
				} else if chosenBlockSize < MinimumBlockSize {
					return ErrMinimumBlockSize
				}

				if !((chosenBlockSize > 0) && ((chosenBlockSize & (chosenBlockSize - 1)) == 0)) {
					return ErrBlockSizeNotPowerOfTwo
				}
			default:
				return ErrUnknownInfo
			}
		case protocol.NEGOTIATION_TYPE_REPLY_ACK:
			break n
		case protocol.NEGOTIATION_TYPE_REPLY_ERR_UNKNOWN:
			return ErrUnknownErr
		default:
			return ErrUnknownReply
		}
	}

	if _, _, err := syscall.Syscall(
		syscall.SYS_IOCTL,
		device.Fd(),
		ioctl.NEGOTIATION_IOCTL_SET_BLOCKSIZE,
		uintptr(chosenBlockSize),
	); err != 0 {
		return err
	}

	if _, _, err := syscall.Syscall(
		syscall.SYS_IOCTL,
		device.Fd(),
		ioctl.NEGOTIATION_IOCTL_SET_SIZE_BLOCKS,
		uintptr(size/uint64(chosenBlockSize)),
	); err != 0 {
		return err
	}

	if _, _, err := syscall.Syscall(
		syscall.SYS_IOCTL,
		device.Fd(),
		ioctl.NEGOTIATION_IOCTL_SET_TIMEOUT,
		uintptr(options.Timeout),
	); err != 0 {
		return err
	}

	go func() {
		defer func() {
			close(fatal)
		}()

		if _, _, err := syscall.Syscall(
			syscall.SYS_IOCTL,
			device.Fd(),
			ioctl.NEGOTIATION_IOCTL_DO_IT,
			0,
		); err != 0 {
			fatal <- err

			return
		}
	}()

	return <-fatal
}

func Disconnect(device *os.File) error {
	if _, _, err := syscall.Syscall(
		syscall.SYS_IOCTL,
		device.Fd(),
		ioctl.TRANSMISSION_IOCTL_CLEAR_QUE,
		0,
	); err != 0 {
		return err
	}

	if _, _, err := syscall.Syscall(
		syscall.SYS_IOCTL,
		device.Fd(),
		ioctl.TRANSMISSION_IOCTL_DISCONNECT,
		0,
	); err != 0 {
		return err
	}

	if _, _, err := syscall.Syscall(
		syscall.SYS_IOCTL,
		device.Fd(),
		ioctl.TRANSMISSION_IOCTL_CLEAR_SOCK,
		0,
	); err != 0 {
		return err
	}

	return nil
}

func List(conn net.Conn) ([]string, error) {
	if err := negotiateNewstyle(conn); err != nil {
		return []string{}, err
	}

	if err := binary.Write(conn, binary.BigEndian, protocol.NegotiationOptionHeader{
		OptionMagic: protocol.NEGOTIATION_MAGIC_OPTION,
		ID:          protocol.NEGOTIATION_ID_OPTION_LIST,
		Length:      0,
	}); err != nil {
		return []string{}, err
	}

	var replyHeader protocol.NegotiationReplyHeader
	if err := binary.Read(conn, binary.BigEndian, &replyHeader); err != nil {
		return []string{}, err
	}

	if replyHeader.ReplyMagic != protocol.NEGOTIATION_MAGIC_REPLY {
		return []string{}, server.ErrInvalidMagic
	}

	infoRaw := make([]byte, replyHeader.Length)
	if _, err := io.ReadFull(conn, infoRaw); err != nil {
		return []string{}, err
	}

	info := bytes.NewBuffer(infoRaw)

	exportNames := []string{}
	for {
		var exportNameLength uint32
		if err := binary.Read(info, binary.BigEndian, &exportNameLength); err != nil {
			if errors.Is(err, io.EOF) {
				break
			}

			return []string{}, err
		}

		exportName := make([]byte, exportNameLength)
		if _, err := io.ReadFull(info, exportName); err != nil {
			return []string{}, err
		}

		exportNames = append(exportNames, string(exportName))
	}

	if err := binary.Write(conn, binary.BigEndian, protocol.NegotiationOptionHeader{
		OptionMagic: protocol.NEGOTIATION_MAGIC_OPTION,
		ID:          protocol.NEGOTIATION_ID_OPTION_ABORT,
		Length:      0,
	}); err != nil {
		return []string{}, err
	}

	return exportNames, nil
}


================================================
FILE: pkg/ioctl/negotiation_cgo.go
================================================
//go:build linux && cgo

package ioctl

/*
#include <sys/ioctl.h>
#include <linux/nbd.h>
*/
import "C"

const (
	NEGOTIATION_IOCTL_SET_SOCK        = C.NBD_SET_SOCK
	NEGOTIATION_IOCTL_SET_BLOCKSIZE   = C.NBD_SET_BLKSIZE
	NEGOTIATION_IOCTL_SET_SIZE_BLOCKS = C.NBD_SET_SIZE_BLOCKS
	NEGOTIATION_IOCTL_DO_IT           = C.NBD_DO_IT
	NEGOTIATION_IOCTL_SET_TIMEOUT     = C.NBD_SET_TIMEOUT
)


================================================
FILE: pkg/ioctl/negotiation_go_amd64.go
================================================
//go:build linux && !cgo && amd64

package ioctl

// See /usr/include/linux/nbd.h

const (
	NEGOTIATION_IOCTL_SET_SOCK        = 43776
	NEGOTIATION_IOCTL_SET_BLOCKSIZE   = 43777
	NEGOTIATION_IOCTL_SET_SIZE_BLOCKS = 43783
	NEGOTIATION_IOCTL_DO_IT           = 43779
	NEGOTIATION_IOCTL_SET_TIMEOUT     = 43785
)


================================================
FILE: pkg/ioctl/transmission_cgo.go
================================================
//go:build linux && cgo

package ioctl

/*
#include <sys/ioctl.h>
#include <linux/nbd.h>
*/
import "C"

const (
	TRANSMISSION_IOCTL_DISCONNECT = C.NBD_DISCONNECT
	TRANSMISSION_IOCTL_CLEAR_SOCK = C.NBD_CLEAR_SOCK
	TRANSMISSION_IOCTL_CLEAR_QUE  = C.NBD_CLEAR_QUE
)


================================================
FILE: pkg/ioctl/transmission_go_amd64.go
================================================
//go:build linux && !cgo && amd64

package ioctl

const (
	TRANSMISSION_IOCTL_DISCONNECT = 43784
	TRANSMISSION_IOCTL_CLEAR_SOCK = 43780
	TRANSMISSION_IOCTL_CLEAR_QUE  = 43781
)


================================================
FILE: pkg/protocol/negotiation.go
================================================
package protocol

// See https://github.com/NetworkBlockDevice/nbd/blob/master/doc/proto.md and https://github.com/abligh/gonbdserver/

const (
	NEGOTIATION_MAGIC_OLDSTYLE = uint64(0x4e42444d41474943)
	NEGOTIATION_MAGIC_OPTION   = uint64(0x49484156454F5054)
	NEGOTIATION_MAGIC_REPLY    = uint64(0x3e889045565a9)

	NEGOTIATION_HANDSHAKE_FLAG_FIXED_NEWSTYLE = uint16(1 << 0)

	NEGOTIATION_ID_OPTION_ABORT = uint32(2)
	NEGOTIATION_ID_OPTION_LIST  = uint32(3)
	NEGOTIATION_ID_OPTION_INFO  = uint32(6)
	NEGOTIATION_ID_OPTION_GO    = uint32(7)

	NEGOTIATION_TYPE_REPLY_ACK             = uint32(1)
	NEGOTIATION_TYPE_REPLY_SERVER          = uint32(2)
	NEGOTIATION_TYPE_REPLY_INFO            = uint32(3)
	NEGOTIATION_TYPE_REPLY_ERR_UNSUPPORTED = uint32(1 | uint32(1<<31))
	NEGOTIATION_TYPE_REPLY_ERR_UNKNOWN     = uint32(6 | uint32(1<<31))

	NEGOTIATION_TYPE_INFO_EXPORT      = uint16(0)
	NEGOTIATION_TYPE_INFO_NAME        = uint16(1)
	NEGOTIATION_TYPE_INFO_DESCRIPTION = uint16(2)
	NEGOTIATION_TYPE_INFO_BLOCKSIZE   = uint16(3)

	NEGOTIATION_REPLY_FLAGS_HAS_FLAGS      = uint16((1 << 0))
	NEGOTIATION_REPLY_FLAGS_CAN_MULTI_CONN = uint16((1 << 8))
)

type NegotiationNewstyleHeader struct {
	OldstyleMagic  uint64
	OptionMagic    uint64
	HandshakeFlags uint16
}

type NegotiationOptionHeader struct {
	OptionMagic uint64
	ID          uint32
	Length      uint32
}

type NegotiationReplyHeader struct {
	ReplyMagic uint64
	ID         uint32
	Type       uint32
	Length     uint32
}

type NegotiationReplyInfo struct {
	Type              uint16
	Size              uint64
	TransmissionFlags uint16
}

type NegotiationReplyNameHeader struct {
	Type uint16
}

type NegotiationReplyDescriptionHeader NegotiationReplyNameHeader

type NegotiationReplyBlockSize struct {
	Type               uint16
	MinimumBlockSize   uint32
	PreferredBlockSize uint32
	MaximumBlockSize   uint32
}


================================================
FILE: pkg/protocol/transmission.go
================================================
package protocol

const (
	TRANSMISSION_MAGIC_REQUEST = uint32(0x25609513)
	TRANSMISSION_MAGIC_REPLY   = uint32(0x67446698)

	TRANSMISSION_TYPE_REQUEST_READ  = uint16(0)
	TRANSMISSION_TYPE_REQUEST_WRITE = uint16(1)
	TRANSMISSION_TYPE_REQUEST_DISC  = uint16(2)

	TRANSMISSION_ERROR_EPERM  = uint32(1)
	TRANSMISSION_ERROR_EINVAL = uint32(22)
)

type TransmissionRequestHeader struct {
	RequestMagic uint32
	CommandFlags uint16
	Type         uint16
	Handle       uint64
	Offset       uint64
	Length       uint32
}

type TransmissionReplyHeader struct {
	ReplyMagic uint32
	Error      uint32
	Handle     uint64
}


================================================
FILE: pkg/server/nbd.go
================================================
package server

import (
	"bytes"
	"encoding/binary"
	"errors"
	"io"
	"net"

	"github.com/pojntfx/go-nbd/pkg/backend"
	"github.com/pojntfx/go-nbd/pkg/protocol"
)

var (
	ErrInvalidMagic     = errors.New("invalid magic")
	ErrInvalidBlocksize = errors.New("invalid blocksize")
)

const (
	defaultMaximumRequestSize = 32 * 1024 * 1024 // Support for a 32M maximum packet size is expected: https://sourceforge.net/p/nbd/mailman/message/35081223/
)

type Export struct {
	Name        string
	Description string

	Backend backend.Backend
}

type Options struct {
	ReadOnly bool

	MinimumBlockSize   uint32
	PreferredBlockSize uint32
	MaximumBlockSize   uint32

	MaximumRequestSize int
	SupportsMultiConn  bool
}

func Handle(conn net.Conn, exports []*Export, options *Options) error {
	if options == nil {
		options = &Options{
			ReadOnly:          false,
			SupportsMultiConn: true,
		}
	}

	if options.MinimumBlockSize == 0 {
		options.MinimumBlockSize = 1
	}

	if options.PreferredBlockSize == 0 {
		options.PreferredBlockSize = 4096
	}

	if options.MaximumBlockSize == 0 {
		options.MaximumBlockSize = defaultMaximumRequestSize
	}

	if options.MaximumRequestSize == 0 {
		options.MaximumRequestSize = defaultMaximumRequestSize
	}

	// Negotiation
	if err := binary.Write(conn, binary.BigEndian, protocol.NegotiationNewstyleHeader{
		OldstyleMagic:  protocol.NEGOTIATION_MAGIC_OLDSTYLE,
		OptionMagic:    protocol.NEGOTIATION_MAGIC_OPTION,
		HandshakeFlags: protocol.NEGOTIATION_HANDSHAKE_FLAG_FIXED_NEWSTYLE,
	}); err != nil {
		return err
	}

	_, err := io.CopyN(io.Discard, conn, 4) // Discard client flags (uint32)
	if err != nil {
		return err
	}

	var export *Export
n:
	for {
		var optionHeader protocol.NegotiationOptionHeader
		if err := binary.Read(conn, binary.BigEndian, &optionHeader); err != nil {
			return err
		}

		if optionHeader.OptionMagic != protocol.NEGOTIATION_MAGIC_OPTION {
			return ErrInvalidMagic
		}

		switch optionHeader.ID {
		case protocol.NEGOTIATION_ID_OPTION_INFO, protocol.NEGOTIATION_ID_OPTION_GO:
			var exportNameLength uint32
			if err := binary.Read(conn, binary.BigEndian, &exportNameLength); err != nil {
				return err
			}

			exportName := make([]byte, exportNameLength)
			if _, err := io.ReadFull(conn, exportName); err != nil {
				return err
			}

			for _, candidate := range exports {
				if candidate.Name == string(exportName) {
					export = candidate

					break
				}
			}

			if export == nil {
				if length := int64(optionHeader.Length) - 4 - int64(exportNameLength); length > 0 { // Discard the option's data, minus the export name length and export name we've already read
					_, err := io.CopyN(io.Discard, conn, length)
					if err != nil {
						return err
					}
				}

				if err := binary.Write(conn, binary.BigEndian, protocol.NegotiationReplyHeader{
					ReplyMagic: protocol.NEGOTIATION_MAGIC_REPLY,
					ID:         optionHeader.ID,
					Type:       protocol.NEGOTIATION_TYPE_REPLY_ERR_UNKNOWN,
					Length:     0,
				}); err != nil {
					return err
				}

				break
			}

			size, err := export.Backend.Size()
			if err != nil {
				return err
			}

			{
				var informationRequestCount uint16
				if err := binary.Read(conn, binary.BigEndian, &informationRequestCount); err != nil {
					return err
				}

				_, err := io.CopyN(io.Discard, conn, 2*int64(informationRequestCount)) // Discard information requests (uint16s)
				if err != nil {
					return err
				}
			}

			{
				transmissionFlags := uint16(0)
				if options.SupportsMultiConn {
					transmissionFlags = protocol.NEGOTIATION_REPLY_FLAGS_HAS_FLAGS | protocol.NEGOTIATION_REPLY_FLAGS_CAN_MULTI_CONN
				}

				info := &bytes.Buffer{}
				if err := binary.Write(info, binary.BigEndian, protocol.NegotiationReplyInfo{
					Type:              protocol.NEGOTIATION_TYPE_INFO_EXPORT,
					Size:              uint64(size),
					TransmissionFlags: transmissionFlags,
				}); err != nil {
					return err
				}

				if err := binary.Write(conn, binary.BigEndian, protocol.NegotiationReplyHeader{
					ReplyMagic: protocol.NEGOTIATION_MAGIC_REPLY,
					ID:         optionHeader.ID,
					Type:       protocol.NEGOTIATION_TYPE_REPLY_INFO,
					Length:     uint32(info.Len()),
				}); err != nil {
					return err
				}

				if _, err := io.Copy(conn, info); err != nil {
					return err
				}
			}

			{
				info := &bytes.Buffer{}
				if err := binary.Write(info, binary.BigEndian, protocol.NegotiationReplyNameHeader{
					Type: protocol.NEGOTIATION_TYPE_INFO_NAME,
				}); err != nil {
					return err
				}

				if _, err := info.Write([]byte(exportName)); err != nil {
					return err
				}

				if err := binary.Write(conn, binary.BigEndian, protocol.NegotiationReplyHeader{
					ReplyMagic: protocol.NEGOTIATION_MAGIC_REPLY,
					ID:         optionHeader.ID,
					Type:       protocol.NEGOTIATION_TYPE_REPLY_INFO,
					Length:     uint32(info.Len()),
				}); err != nil {
					return err
				}

				if _, err := io.Copy(conn, info); err != nil {
					return err
				}
			}

			{
				info := &bytes.Buffer{}
				if err := binary.Write(info, binary.BigEndian, protocol.NegotiationReplyDescriptionHeader{
					Type: protocol.NEGOTIATION_TYPE_INFO_DESCRIPTION,
				}); err != nil {
					return err
				}

				if err := binary.Write(info, binary.BigEndian, []byte(export.Description)); err != nil {
					return err
				}

				if err := binary.Write(conn, binary.BigEndian, protocol.NegotiationReplyHeader{
					ReplyMagic: protocol.NEGOTIATION_MAGIC_REPLY,
					ID:         optionHeader.ID,
					Type:       protocol.NEGOTIATION_TYPE_REPLY_INFO,
					Length:     uint32(info.Len()),
				}); err != nil {
					return err
				}

				if _, err := io.Copy(conn, info); err != nil {
					return err
				}
			}

			{
				info := &bytes.Buffer{}
				if err := binary.Write(info, binary.BigEndian, protocol.NegotiationReplyBlockSize{
					Type:               protocol.NEGOTIATION_TYPE_INFO_BLOCKSIZE,
					MinimumBlockSize:   options.MinimumBlockSize,
					PreferredBlockSize: options.PreferredBlockSize,
					MaximumBlockSize:   options.MaximumBlockSize,
				}); err != nil {
					return err
				}

				if err := binary.Write(conn, binary.BigEndian, protocol.NegotiationReplyHeader{
					ReplyMagic: protocol.NEGOTIATION_MAGIC_REPLY,
					ID:         optionHeader.ID,
					Type:       protocol.NEGOTIATION_TYPE_REPLY_INFO,
					Length:     uint32(info.Len()),
				}); err != nil {
					return err
				}

				if _, err := io.Copy(conn, info); err != nil {
					return err
				}
			}

			if err := binary.Write(conn, binary.BigEndian, protocol.NegotiationReplyHeader{
				ReplyMagic: protocol.NEGOTIATION_MAGIC_REPLY,
				ID:         optionHeader.ID,
				Type:       protocol.NEGOTIATION_TYPE_REPLY_ACK,
				Length:     0,
			}); err != nil {
				return err
			}

			if optionHeader.ID == protocol.NEGOTIATION_ID_OPTION_GO {
				break n
			}
		case protocol.NEGOTIATION_ID_OPTION_ABORT:
			if err := binary.Write(conn, binary.BigEndian, protocol.NegotiationReplyHeader{
				ReplyMagic: protocol.NEGOTIATION_MAGIC_REPLY,
				ID:         optionHeader.ID,
				Type:       protocol.NEGOTIATION_TYPE_REPLY_ACK,
				Length:     0,
			}); err != nil {
				return err
			}

			return nil
		case protocol.NEGOTIATION_ID_OPTION_LIST:
			{
				info := &bytes.Buffer{}

				for _, export := range exports {
					exportName := []byte(export.Name)

					if err := binary.Write(info, binary.BigEndian, uint32(len(exportName))); err != nil {
						return err
					}

					if err := binary.Write(info, binary.BigEndian, exportName); err != nil {
						return err
					}
				}

				if err := binary.Write(conn, binary.BigEndian, protocol.NegotiationReplyHeader{
					ReplyMagic: protocol.NEGOTIATION_MAGIC_REPLY,
					ID:         optionHeader.ID,
					Type:       protocol.NEGOTIATION_TYPE_REPLY_SERVER,
					Length:     uint32(info.Len()),
				}); err != nil {
					return err
				}

				if _, err := io.Copy(conn, info); err != nil {
					return err
				}
			}

			if err := binary.Write(conn, binary.BigEndian, protocol.NegotiationReplyHeader{
				ReplyMagic: protocol.NEGOTIATION_MAGIC_REPLY,
				ID:         optionHeader.ID,
				Type:       protocol.NEGOTIATION_TYPE_REPLY_ACK,
				Length:     0,
			}); err != nil {
				return err
			}
		default:
			_, err := io.CopyN(io.Discard, conn, int64(optionHeader.Length)) // Discard the unknown option's data
			if err != nil {
				return err
			}

			if err := binary.Write(conn, binary.BigEndian, protocol.NegotiationReplyHeader{
				ReplyMagic: protocol.NEGOTIATION_MAGIC_REPLY,
				ID:         optionHeader.ID,
				Type:       protocol.NEGOTIATION_TYPE_REPLY_ERR_UNSUPPORTED,
				Length:     0,
			}); err != nil {
				return err
			}
		}
	}

	// Transmission
	b := []byte{}
	for {
		var requestHeader protocol.TransmissionRequestHeader
		if err := binary.Read(conn, binary.BigEndian, &requestHeader); err != nil {
			return err
		}

		if requestHeader.RequestMagic != protocol.TRANSMISSION_MAGIC_REQUEST {
			return ErrInvalidMagic
		}

		length := requestHeader.Length
		if length > defaultMaximumRequestSize {
			return ErrInvalidBlocksize
		}

		if length != uint32(len(b)) {
			b = make([]byte, length)
		}

		switch requestHeader.Type {
		case protocol.TRANSMISSION_TYPE_REQUEST_READ:
			if err := binary.Write(conn, binary.BigEndian, protocol.TransmissionReplyHeader{
				ReplyMagic: protocol.TRANSMISSION_MAGIC_REPLY,
				Error:      0,
				Handle:     requestHeader.Handle,
			}); err != nil {
				return err
			}

			n, err := export.Backend.ReadAt(b[:length], int64(requestHeader.Offset))
			if err != nil {
				return err
			}

			if _, err := conn.Write(b[:n]); err != nil {
				return err
			}
		case protocol.TRANSMISSION_TYPE_REQUEST_WRITE:
			if options.ReadOnly {
				_, err := io.CopyN(io.Discard, conn, int64(requestHeader.Length)) // Discard the write command's data
				if err != nil {
					return err
				}

				if err := binary.Write(conn, binary.BigEndian, protocol.TransmissionReplyHeader{
					ReplyMagic: protocol.TRANSMISSION_MAGIC_REPLY,
					Error:      protocol.TRANSMISSION_ERROR_EPERM,
					Handle:     requestHeader.Handle,
				}); err != nil {
					return err
				}

				break
			}

			n, err := io.ReadAtLeast(conn, b[:length], int(requestHeader.Length))
			if err != nil {
				return err
			}

			if _, err := export.Backend.WriteAt(b[:n], int64(requestHeader.Offset)); err != nil {
				return err
			}

			if err := binary.Write(conn, binary.BigEndian, protocol.TransmissionReplyHeader{
				ReplyMagic: protocol.TRANSMISSION_MAGIC_REPLY,
				Error:      0,
				Handle:     requestHeader.Handle,
			}); err != nil {
				return err
			}
		case protocol.TRANSMISSION_TYPE_REQUEST_DISC:
			if !options.ReadOnly {
				if err := export.Backend.Sync(); err != nil {
					return err
				}
			}

			return nil
		default:
			_, err := io.CopyN(io.Discard, conn, int64(requestHeader.Length)) // Discard the unknown command's data
			if err != nil {
				return err
			}

			if err := binary.Write(conn, binary.BigEndian, protocol.TransmissionReplyHeader{
				ReplyMagic: protocol.TRANSMISSION_MAGIC_REPLY,
				Error:      protocol.TRANSMISSION_ERROR_EINVAL,
				Handle:     requestHeader.Handle,
			}); err != nil {
				return err
			}
		}
	}
}
Download .txt
gitextract_lo09498w/

├── .github/
│   └── workflows/
│       └── hydrun.yaml
├── .gitignore
├── Hydrunfile
├── LICENSE
├── Makefile
├── README.md
├── cmd/
│   ├── go-nbd-example-client/
│   │   └── main.go
│   ├── go-nbd-example-server-file/
│   │   └── main.go
│   └── go-nbd-example-server-memory/
│       └── main.go
├── go.mod
├── go.sum
└── pkg/
    ├── backend/
    │   ├── backend.go
    │   ├── file.go
    │   └── memory.go
    ├── client/
    │   └── nbd.go
    ├── ioctl/
    │   ├── negotiation_cgo.go
    │   ├── negotiation_go_amd64.go
    │   ├── transmission_cgo.go
    │   └── transmission_go_amd64.go
    ├── protocol/
    │   ├── negotiation.go
    │   └── transmission.go
    └── server/
        └── nbd.go
Download .txt
SYMBOL INDEX (78 symbols across 14 files)

FILE: cmd/go-nbd-example-client/main.go
  function main (line 14) | func main() {

FILE: cmd/go-nbd-example-server-file/main.go
  function main (line 14) | func main() {

FILE: cmd/go-nbd-example-server-memory/main.go
  function main (line 13) | func main() {

FILE: pkg/backend/backend.go
  type Backend (line 5) | type Backend interface

FILE: pkg/backend/file.go
  type FileBackend (line 9) | type FileBackend struct
    method ReadAt (line 18) | func (b *FileBackend) ReadAt(p []byte, off int64) (n int, err error) {
    method WriteAt (line 28) | func (b *FileBackend) WriteAt(p []byte, off int64) (n int, err error) {
    method Size (line 38) | func (b *FileBackend) Size() (int64, error) {
    method Sync (line 47) | func (b *FileBackend) Sync() error {
  function NewFileBackend (line 14) | func NewFileBackend(file *os.File) *FileBackend {

FILE: pkg/backend/memory.go
  type MemoryBackend (line 8) | type MemoryBackend struct
    method ReadAt (line 17) | func (b *MemoryBackend) ReadAt(p []byte, off int64) (n int, err error) {
    method WriteAt (line 31) | func (b *MemoryBackend) WriteAt(p []byte, off int64) (n int, err error) {
    method Size (line 49) | func (b *MemoryBackend) Size() (int64, error) {
    method Sync (line 53) | func (b *MemoryBackend) Sync() error {
  function NewMemoryBackend (line 13) | func NewMemoryBackend(memory []byte) *MemoryBackend {

FILE: pkg/client/nbd.go
  constant MinimumBlockSize (line 23) | MinimumBlockSize = 512
  constant MaximumBlockSize (line 24) | MaximumBlockSize = 4096
  type Options (line 38) | type Options struct
  function negotiateNewstyle (line 47) | func negotiateNewstyle(conn net.Conn) error {
  function Connect (line 68) | func Connect(conn net.Conn, device *os.File, options *Options) error {
  function Disconnect (line 337) | func Disconnect(device *os.File) error {
  function List (line 368) | func List(conn net.Conn) ([]string, error) {

FILE: pkg/ioctl/negotiation_cgo.go
  constant NEGOTIATION_IOCTL_SET_SOCK (line 12) | NEGOTIATION_IOCTL_SET_SOCK        = C.NBD_SET_SOCK
  constant NEGOTIATION_IOCTL_SET_BLOCKSIZE (line 13) | NEGOTIATION_IOCTL_SET_BLOCKSIZE   = C.NBD_SET_BLKSIZE
  constant NEGOTIATION_IOCTL_SET_SIZE_BLOCKS (line 14) | NEGOTIATION_IOCTL_SET_SIZE_BLOCKS = C.NBD_SET_SIZE_BLOCKS
  constant NEGOTIATION_IOCTL_DO_IT (line 15) | NEGOTIATION_IOCTL_DO_IT           = C.NBD_DO_IT
  constant NEGOTIATION_IOCTL_SET_TIMEOUT (line 16) | NEGOTIATION_IOCTL_SET_TIMEOUT     = C.NBD_SET_TIMEOUT

FILE: pkg/ioctl/negotiation_go_amd64.go
  constant NEGOTIATION_IOCTL_SET_SOCK (line 8) | NEGOTIATION_IOCTL_SET_SOCK        = 43776
  constant NEGOTIATION_IOCTL_SET_BLOCKSIZE (line 9) | NEGOTIATION_IOCTL_SET_BLOCKSIZE   = 43777
  constant NEGOTIATION_IOCTL_SET_SIZE_BLOCKS (line 10) | NEGOTIATION_IOCTL_SET_SIZE_BLOCKS = 43783
  constant NEGOTIATION_IOCTL_DO_IT (line 11) | NEGOTIATION_IOCTL_DO_IT           = 43779
  constant NEGOTIATION_IOCTL_SET_TIMEOUT (line 12) | NEGOTIATION_IOCTL_SET_TIMEOUT     = 43785

FILE: pkg/ioctl/transmission_cgo.go
  constant TRANSMISSION_IOCTL_DISCONNECT (line 12) | TRANSMISSION_IOCTL_DISCONNECT = C.NBD_DISCONNECT
  constant TRANSMISSION_IOCTL_CLEAR_SOCK (line 13) | TRANSMISSION_IOCTL_CLEAR_SOCK = C.NBD_CLEAR_SOCK
  constant TRANSMISSION_IOCTL_CLEAR_QUE (line 14) | TRANSMISSION_IOCTL_CLEAR_QUE  = C.NBD_CLEAR_QUE

FILE: pkg/ioctl/transmission_go_amd64.go
  constant TRANSMISSION_IOCTL_DISCONNECT (line 6) | TRANSMISSION_IOCTL_DISCONNECT = 43784
  constant TRANSMISSION_IOCTL_CLEAR_SOCK (line 7) | TRANSMISSION_IOCTL_CLEAR_SOCK = 43780
  constant TRANSMISSION_IOCTL_CLEAR_QUE (line 8) | TRANSMISSION_IOCTL_CLEAR_QUE  = 43781

FILE: pkg/protocol/negotiation.go
  constant NEGOTIATION_MAGIC_OLDSTYLE (line 6) | NEGOTIATION_MAGIC_OLDSTYLE = uint64(0x4e42444d41474943)
  constant NEGOTIATION_MAGIC_OPTION (line 7) | NEGOTIATION_MAGIC_OPTION   = uint64(0x49484156454F5054)
  constant NEGOTIATION_MAGIC_REPLY (line 8) | NEGOTIATION_MAGIC_REPLY    = uint64(0x3e889045565a9)
  constant NEGOTIATION_HANDSHAKE_FLAG_FIXED_NEWSTYLE (line 10) | NEGOTIATION_HANDSHAKE_FLAG_FIXED_NEWSTYLE = uint16(1 << 0)
  constant NEGOTIATION_ID_OPTION_ABORT (line 12) | NEGOTIATION_ID_OPTION_ABORT = uint32(2)
  constant NEGOTIATION_ID_OPTION_LIST (line 13) | NEGOTIATION_ID_OPTION_LIST  = uint32(3)
  constant NEGOTIATION_ID_OPTION_INFO (line 14) | NEGOTIATION_ID_OPTION_INFO  = uint32(6)
  constant NEGOTIATION_ID_OPTION_GO (line 15) | NEGOTIATION_ID_OPTION_GO    = uint32(7)
  constant NEGOTIATION_TYPE_REPLY_ACK (line 17) | NEGOTIATION_TYPE_REPLY_ACK             = uint32(1)
  constant NEGOTIATION_TYPE_REPLY_SERVER (line 18) | NEGOTIATION_TYPE_REPLY_SERVER          = uint32(2)
  constant NEGOTIATION_TYPE_REPLY_INFO (line 19) | NEGOTIATION_TYPE_REPLY_INFO            = uint32(3)
  constant NEGOTIATION_TYPE_REPLY_ERR_UNSUPPORTED (line 20) | NEGOTIATION_TYPE_REPLY_ERR_UNSUPPORTED = uint32(1 | uint32(1<<31))
  constant NEGOTIATION_TYPE_REPLY_ERR_UNKNOWN (line 21) | NEGOTIATION_TYPE_REPLY_ERR_UNKNOWN     = uint32(6 | uint32(1<<31))
  constant NEGOTIATION_TYPE_INFO_EXPORT (line 23) | NEGOTIATION_TYPE_INFO_EXPORT      = uint16(0)
  constant NEGOTIATION_TYPE_INFO_NAME (line 24) | NEGOTIATION_TYPE_INFO_NAME        = uint16(1)
  constant NEGOTIATION_TYPE_INFO_DESCRIPTION (line 25) | NEGOTIATION_TYPE_INFO_DESCRIPTION = uint16(2)
  constant NEGOTIATION_TYPE_INFO_BLOCKSIZE (line 26) | NEGOTIATION_TYPE_INFO_BLOCKSIZE   = uint16(3)
  constant NEGOTIATION_REPLY_FLAGS_HAS_FLAGS (line 28) | NEGOTIATION_REPLY_FLAGS_HAS_FLAGS      = uint16((1 << 0))
  constant NEGOTIATION_REPLY_FLAGS_CAN_MULTI_CONN (line 29) | NEGOTIATION_REPLY_FLAGS_CAN_MULTI_CONN = uint16((1 << 8))
  type NegotiationNewstyleHeader (line 32) | type NegotiationNewstyleHeader struct
  type NegotiationOptionHeader (line 38) | type NegotiationOptionHeader struct
  type NegotiationReplyHeader (line 44) | type NegotiationReplyHeader struct
  type NegotiationReplyInfo (line 51) | type NegotiationReplyInfo struct
  type NegotiationReplyNameHeader (line 57) | type NegotiationReplyNameHeader struct
  type NegotiationReplyDescriptionHeader (line 61) | type NegotiationReplyDescriptionHeader
  type NegotiationReplyBlockSize (line 63) | type NegotiationReplyBlockSize struct

FILE: pkg/protocol/transmission.go
  constant TRANSMISSION_MAGIC_REQUEST (line 4) | TRANSMISSION_MAGIC_REQUEST = uint32(0x25609513)
  constant TRANSMISSION_MAGIC_REPLY (line 5) | TRANSMISSION_MAGIC_REPLY   = uint32(0x67446698)
  constant TRANSMISSION_TYPE_REQUEST_READ (line 7) | TRANSMISSION_TYPE_REQUEST_READ  = uint16(0)
  constant TRANSMISSION_TYPE_REQUEST_WRITE (line 8) | TRANSMISSION_TYPE_REQUEST_WRITE = uint16(1)
  constant TRANSMISSION_TYPE_REQUEST_DISC (line 9) | TRANSMISSION_TYPE_REQUEST_DISC  = uint16(2)
  constant TRANSMISSION_ERROR_EPERM (line 11) | TRANSMISSION_ERROR_EPERM  = uint32(1)
  constant TRANSMISSION_ERROR_EINVAL (line 12) | TRANSMISSION_ERROR_EINVAL = uint32(22)
  type TransmissionRequestHeader (line 15) | type TransmissionRequestHeader struct
  type TransmissionReplyHeader (line 24) | type TransmissionReplyHeader struct

FILE: pkg/server/nbd.go
  constant defaultMaximumRequestSize (line 20) | defaultMaximumRequestSize = 32 * 1024 * 1024
  type Export (line 23) | type Export struct
  type Options (line 30) | type Options struct
  function Handle (line 41) | func Handle(conn net.Conn, exports []*Export, options *Options) error {
Condensed preview — 22 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (59K chars).
[
  {
    "path": ".github/workflows/hydrun.yaml",
    "chars": 2702,
    "preview": "name: hydrun CI\n\non:\n  push:\n  pull_request:\n  schedule:\n    - cron: \"0 0 * * 0\"\n\njobs:\n  build-linux:\n    runs-on: ${{ "
  },
  {
    "path": ".gitignore",
    "chars": 4,
    "preview": "out\n"
  },
  {
    "path": "Hydrunfile",
    "chars": 201,
    "preview": "#!/bin/bash\n\nset -e\n\n# Test\nif [ \"$1\" = \"test\" ]; then\n  # Configure Git\n  git config --global --add safe.directory '*'\n"
  },
  {
    "path": "LICENSE",
    "chars": 10280,
    "preview": "Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AN"
  },
  {
    "path": "Makefile",
    "chars": 997,
    "preview": "# Public variables\nDESTDIR ?=\nPREFIX ?= /usr/local\nOUTPUT_DIR ?= out\nDST ?=\n\n# Private variables\nobj = go-nbd-example-cl"
  },
  {
    "path": "README.md",
    "chars": 6059,
    "preview": "<img alt=\"Project icon\" style=\"vertical-align: middle;\" src=\"./docs/icon.svg\" width=\"128\" height=\"128\" align=\"left\">\n\n# "
  },
  {
    "path": "cmd/go-nbd-example-client/main.go",
    "chars": 1350,
    "preview": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"flag\"\n\t\"log\"\n\t\"net\"\n\t\"os\"\n\t\"os/signal\"\n\n\t\"github.com/pojntfx/go-nbd/pkg/client"
  },
  {
    "path": "cmd/go-nbd-example-server-file/main.go",
    "chars": 2325,
    "preview": "package main\n\nimport (\n\t\"flag\"\n\t\"log\"\n\t\"net\"\n\t\"os\"\n\n\t\"github.com/pojntfx/go-nbd/pkg/backend\"\n\t\"github.com/pojntfx/go-nbd"
  },
  {
    "path": "cmd/go-nbd-example-server-memory/main.go",
    "chars": 2120,
    "preview": "package main\n\nimport (\n\t\"flag\"\n\t\"log\"\n\t\"net\"\n\n\t\"github.com/pojntfx/go-nbd/pkg/backend\"\n\t\"github.com/pojntfx/go-nbd/pkg/c"
  },
  {
    "path": "go.mod",
    "chars": 87,
    "preview": "module github.com/pojntfx/go-nbd\n\ngo 1.20\n\nrequire github.com/pilebones/go-udev v0.9.0\n"
  },
  {
    "path": "go.sum",
    "chars": 419,
    "preview": "github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\ngithub.com/kr/pty v1.1.1/go.mod h1:pF"
  },
  {
    "path": "pkg/backend/backend.go",
    "chars": 121,
    "preview": "package backend\n\nimport \"io\"\n\ntype Backend interface {\n\tio.ReaderAt\n\tio.WriterAt\n\n\tSize() (int64, error)\n\tSync() error\n}"
  },
  {
    "path": "pkg/backend/file.go",
    "chars": 717,
    "preview": "package backend\n\nimport (\n\t\"io\"\n\t\"os\"\n\t\"sync\"\n)\n\ntype FileBackend struct {\n\tfile *os.File\n\tlock sync.RWMutex\n}\n\nfunc New"
  },
  {
    "path": "pkg/backend/memory.go",
    "chars": 843,
    "preview": "package backend\n\nimport (\n\t\"io\"\n\t\"sync\"\n)\n\ntype MemoryBackend struct {\n\tmemory []byte\n\tlock   sync.Mutex\n}\n\nfunc NewMemo"
  },
  {
    "path": "pkg/client/nbd.go",
    "chars": 9619,
    "preview": "package client\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"io\"\n\t\"net\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\""
  },
  {
    "path": "pkg/ioctl/negotiation_cgo.go",
    "chars": 384,
    "preview": "//go:build linux && cgo\n\npackage ioctl\n\n/*\n#include <sys/ioctl.h>\n#include <linux/nbd.h>\n*/\nimport \"C\"\n\nconst (\n\tNEGOTIA"
  },
  {
    "path": "pkg/ioctl/negotiation_go_amd64.go",
    "chars": 308,
    "preview": "//go:build linux && !cgo && amd64\n\npackage ioctl\n\n// See /usr/include/linux/nbd.h\n\nconst (\n\tNEGOTIATION_IOCTL_SET_SOCK  "
  },
  {
    "path": "pkg/ioctl/transmission_cgo.go",
    "chars": 263,
    "preview": "//go:build linux && cgo\n\npackage ioctl\n\n/*\n#include <sys/ioctl.h>\n#include <linux/nbd.h>\n*/\nimport \"C\"\n\nconst (\n\tTRANSMI"
  },
  {
    "path": "pkg/ioctl/transmission_go_amd64.go",
    "chars": 177,
    "preview": "//go:build linux && !cgo && amd64\n\npackage ioctl\n\nconst (\n\tTRANSMISSION_IOCTL_DISCONNECT = 43784\n\tTRANSMISSION_IOCTL_CLE"
  },
  {
    "path": "pkg/protocol/negotiation.go",
    "chars": 1861,
    "preview": "package protocol\n\n// See https://github.com/NetworkBlockDevice/nbd/blob/master/doc/proto.md and https://github.com/ablig"
  },
  {
    "path": "pkg/protocol/transmission.go",
    "chars": 609,
    "preview": "package protocol\n\nconst (\n\tTRANSMISSION_MAGIC_REQUEST = uint32(0x25609513)\n\tTRANSMISSION_MAGIC_REPLY   = uint32(0x674466"
  },
  {
    "path": "pkg/server/nbd.go",
    "chars": 11231,
    "preview": "package server\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"io\"\n\t\"net\"\n\n\t\"github.com/pojntfx/go-nbd/pkg/backend\"\n\t\""
  }
]

About this extraction

This page contains the full source code of the pojntfx/go-nbd GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 22 files (51.4 KB), approximately 14.5k tokens, and a symbol index with 78 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!