summaryrefslogtreecommitdiff
path: root/toolbox/toolbox_linux.go
diff options
context:
space:
mode:
Diffstat (limited to 'toolbox/toolbox_linux.go')
-rw-r--r--toolbox/toolbox_linux.go181
1 files changed, 181 insertions, 0 deletions
diff --git a/toolbox/toolbox_linux.go b/toolbox/toolbox_linux.go
new file mode 100644
index 0000000..d35425e
--- /dev/null
+++ b/toolbox/toolbox_linux.go
@@ -0,0 +1,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
+}