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
}
|