// SPDX-FileCopyrightText: 2026 Terin Stock // 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 }