summaryrefslogtreecommitdiff
path: root/toolbox/toolbox_linux.go
blob: d35425e78833ca8c4eda15bb67ffe1a80f524246 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
// SPDX-FileCopyrightText: 2026 Terin Stock <terinjokes@gmail.com>
// SPDX-License-Identifier: EUPL-1.2

// Package toolbox is implemented on Linux using the SCSI Generic (sg) driver, version 3. This
// driver has been included since Linux 2.4, and harmonizes access to SCSI devices. This
// implementation supports access via the high level driver paths (eg, /dev/sr0, /dev/st0) as well
// as the generic paths (eg, /dev/sg0). Raw SCSI commands are sent to the device using the SCSI
// Generic SG_IO ioctl.
package toolbox

import (
	"fmt"
	"os"
	"structs"
	"unsafe"

	"golang.org/x/sys/unix"
)

const (
	SG_GET_VERSION_NUM = 0x2282
	SG_IO              = 0x2285
)

type sgIoHdr struct {
	_ structs.HostLayout

	interfaceID    int32
	dxferDirection int32
	cmdLen         uint8
	mxSbLen        uint8
	iovecCount     uint16
	dxferLen       uint32
	dxferp         *byte
	cmdp           *uint8
	sbp            *byte
	timeout        uint32
	flags          uint32
	packId         int32
	userPtr        *byte
	status         uint8
	maskedStatus   uint8
	msgStatus      uint8
	sbLenWr        uint8
	hostStatus     uint16
	driverStatus   uint16
	resId          int32
	duration       uint32
	info           uint32
}

func ioctlSgIo(fd uintptr, hdr *sgIoHdr) error {
	_, _, errNo := unix.Syscall(unix.SYS_IOCTL, fd, SG_IO, uintptr(unsafe.Pointer(hdr)))
	if errNo != 0 {
		return errNo
	}
	return nil
}

// Device holds an open reference to teh sg device.
type Device struct {
	f *os.File
}

// Open creats a connection to the sg device at devpath, which can be a high level
// device path (such as /dev/sr0) or the generic device path (such as /dev/sg0).
// Returns an error if the device cannot be opened, is not a SCSI device, or if
// the sg driver is too old (older than Linux 2.4).
func Open(devpath string) (*Device, error) {
	f, err := os.OpenFile(devpath, os.O_RDONLY, 0)
	if err != nil {
		return nil, err
	}

	version, err := unix.IoctlGetUint32(int(f.Fd()), SG_GET_VERSION_NUM)
	if err != nil {
		return nil, err
	}
	if version < 30_000 {
		return nil, fmt.Errorf("requires a SCSI device or newer sg dervier")
	}

	return &Device{f: f}, nil
}

// Close closes the refernce to the sg driver.
func (d *Device) Close() error {
	return d.f.Close()
}

// CountCDs returns the count of files recognized by the emulator.
func (d *Device) CountCDs() (int, error) {
	dxferBuf := byte(0)
	cmdp := []byte{TOOLBOX_COUNT_CDS, 0, 0, 0, 0, 0}

	ioHdr := &sgIoHdr{
		interfaceID:    int32('S'),
		cmdLen:         uint8(len(cmdp)),
		mxSbLen:        0,
		dxferDirection: -3,
		cmdp:           &cmdp[0],
		dxferp:         &dxferBuf,
		dxferLen:       1,
		timeout:        20_000,
	}

	err := ioctlSgIo(d.f.Fd(), ioHdr)
	if err != nil {
		return 0, err
	}
	return int(dxferBuf), nil
}

// ListCDs returns [FileEntry] instances corresponding to recognized
// files on the emulator.
func (d *Device) ListCDs() ([]FileEntry, error) {
	cdCount, err := d.CountCDs()
	if err != nil {
		return nil, err
	}

	fileEntries := make([]FileEntry, cdCount)
	cmdp := []byte{TOOLBOX_LIST_CDS, 0, 0, 0, 0, 0}

	ioHdr := &sgIoHdr{
		interfaceID:    int32('S'),
		cmdLen:         uint8(len(cmdp)),
		mxSbLen:        0,
		dxferDirection: -3,
		cmdp:           &cmdp[0],
		dxferp:         (*byte)(unsafe.Pointer(&fileEntries[0])),
		dxferLen:       uint32(int(unsafe.Sizeof(FileEntry{})) * cdCount),
		timeout:        20_000,
	}

	err = ioctlSgIo(d.f.Fd(), ioHdr)
	if err != nil {
		return nil, err
	}

	return fileEntries, nil
}

// SetCDByIndex request the emulator to switch media to the specified index.
func (d *Device) SetCDByIndex(idx int) error {
	cmdp := []byte{TOOLBOX_SET_NEXT_CD, byte(idx), 0, 0, 0, 0}

	ioHdr := &sgIoHdr{
		interfaceID:    int32('S'),
		cmdLen:         uint8(len(cmdp)),
		mxSbLen:        0,
		dxferDirection: -3,
		cmdp:           &cmdp[0],
		timeout:        20_000,
	}

	return ioctlSgIo(d.f.Fd(), ioHdr)
}

// ListDevices returns the device types of each emulated SCSI device.
func (d *Device) ListDevices() ([]DeviceType, error) {
	devices := make([]DeviceType, 8)
	cmdp := []byte{TOOLBOX_LIST_DEVICES, 0, 0, 0, 0, 0}
	ioHdr := &sgIoHdr{
		interfaceID:    int32('S'),
		cmdLen:         uint8(len(cmdp)),
		mxSbLen:        0,
		dxferDirection: -3,
		cmdp:           &cmdp[0],
		dxferp:         (*byte)(unsafe.Pointer(&devices[0])),
		dxferLen:       8,
		timeout:        20_000,
	}

	err := ioctlSgIo(d.f.Fd(), ioHdr)
	if err != nil {
		return nil, err
	}

	return devices, nil
}