refactor: extract mount library package, add flake.nix
Move protocol and SCSI code into importable mount/ package with a single public API (Config + Run). Restructure as multi-binary repo with cmd/aten-mount/ CLI wrapper using context-based cancellation. Add Nix flake with go_1_26 for builds and devshell. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
2ae20b8827
commit
826edc817a
11 changed files with 255 additions and 168 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -1 +1,2 @@
|
||||||
aten-mount
|
/aten-mount
|
||||||
|
/result
|
||||||
|
|
|
||||||
44
cmd/aten-mount/main.go
Normal file
44
cmd/aten-mount/main.go
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"git.dsg.is/dsg/aten-ipmi-tools/mount"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
user := flag.String("u", "admin", "BMC username")
|
||||||
|
pass := flag.String("p", "admin", "BMC password")
|
||||||
|
port := flag.Int("port", 623, "BMC virtual media TCP port")
|
||||||
|
flag.Usage = func() {
|
||||||
|
fmt.Fprintf(os.Stderr, "Usage: %s [flags] <bmc-host> <image.iso>\n\nFlags:\n", os.Args[0])
|
||||||
|
flag.PrintDefaults()
|
||||||
|
}
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if flag.NArg() != 2 {
|
||||||
|
flag.Usage()
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
cfg := mount.Config{
|
||||||
|
Host: flag.Arg(0),
|
||||||
|
Port: *port,
|
||||||
|
Username: *user,
|
||||||
|
Password: *pass,
|
||||||
|
ISOPath: flag.Arg(1),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := mount.Run(ctx, cfg); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
27
flake.lock
generated
Normal file
27
flake.lock
generated
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1773110118,
|
||||||
|
"narHash": "sha256-mPAG8phMbCReKSiKAijjjd3v7uVcJOQ75gSjGJjt/Rk=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "e607cb5360ff1234862ac9f8839522becb853bb9",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixpkgs-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
32
flake.nix
Normal file
32
flake.nix
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
{
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs }:
|
||||||
|
let
|
||||||
|
systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
|
||||||
|
forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f {
|
||||||
|
pkgs = nixpkgs.legacyPackages.${system};
|
||||||
|
});
|
||||||
|
in
|
||||||
|
{
|
||||||
|
packages = forAllSystems ({ pkgs }: {
|
||||||
|
aten-mount = pkgs.buildGoModule.override { go = pkgs.go_1_26; } {
|
||||||
|
pname = "aten-mount";
|
||||||
|
version = "0.1.0";
|
||||||
|
src = ./.;
|
||||||
|
vendorHash = null;
|
||||||
|
subPackages = [ "cmd/aten-mount" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
default = self.packages.${pkgs.system}.aten-mount;
|
||||||
|
});
|
||||||
|
|
||||||
|
devShells = forAllSystems ({ pkgs }: {
|
||||||
|
default = pkgs.mkShell {
|
||||||
|
packages = [ pkgs.go_1_26 ];
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
4
go.mod
4
go.mod
|
|
@ -1,3 +1,3 @@
|
||||||
module github.com/example/aten-mount
|
module git.dsg.is/dsg/aten-ipmi-tools
|
||||||
|
|
||||||
go 1.25.7
|
go 1.26
|
||||||
|
|
|
||||||
102
main.go
102
main.go
|
|
@ -1,102 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
user := flag.String("u", "admin", "BMC username")
|
|
||||||
pass := flag.String("p", "admin", "BMC password")
|
|
||||||
port := flag.Int("port", 623, "BMC virtual media TCP port")
|
|
||||||
flag.Usage = func() {
|
|
||||||
fmt.Fprintf(os.Stderr, "Usage: %s [flags] <bmc-host> <image.iso>\n\nFlags:\n", os.Args[0])
|
|
||||||
flag.PrintDefaults()
|
|
||||||
}
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
if flag.NArg() != 2 {
|
|
||||||
flag.Usage()
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
host := flag.Arg(0)
|
|
||||||
isoPath := flag.Arg(1)
|
|
||||||
|
|
||||||
// Open and validate ISO file
|
|
||||||
isoFile, err := os.Open(isoPath)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("open ISO: %v", err)
|
|
||||||
}
|
|
||||||
defer isoFile.Close()
|
|
||||||
|
|
||||||
isoInfo, err := isoFile.Stat()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("stat ISO: %v", err)
|
|
||||||
}
|
|
||||||
isoSize := isoInfo.Size()
|
|
||||||
if isoSize < 0x8054 {
|
|
||||||
log.Fatalf("ISO file too small (%d bytes)", isoSize)
|
|
||||||
}
|
|
||||||
totalSectors := uint32(isoSize / 2048)
|
|
||||||
log.Printf("ISO: %s (%d bytes, %d sectors)", isoPath, isoSize, totalSectors)
|
|
||||||
|
|
||||||
// TCP connect
|
|
||||||
addr := net.JoinHostPort(host, fmt.Sprintf("%d", *port))
|
|
||||||
log.Printf("Connecting to %s...", addr)
|
|
||||||
conn, err := net.DialTimeout("tcp", addr, 10*time.Second)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("connect: %v", err)
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
log.Printf("Connected")
|
|
||||||
|
|
||||||
// Send PlugIn packet
|
|
||||||
pluginPkt := buildPlugInPacket(*user, *pass)
|
|
||||||
if _, err := conn.Write(pluginPkt); err != nil {
|
|
||||||
log.Fatalf("send plugin: %v", err)
|
|
||||||
}
|
|
||||||
log.Printf("Sent PlugIn packet (%d bytes)", len(pluginPkt))
|
|
||||||
|
|
||||||
// Read mount status
|
|
||||||
status, err := readMountStatus(conn)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("mount status: %v", err)
|
|
||||||
}
|
|
||||||
if status != 0x00 {
|
|
||||||
log.Fatalf("mount failed: %s", mountStatusString(status))
|
|
||||||
}
|
|
||||||
log.Printf("Mount OK")
|
|
||||||
|
|
||||||
// Send SetEP command
|
|
||||||
if _, err := conn.Write(buildSetEPPacket()); err != nil {
|
|
||||||
log.Fatalf("send setep: %v", err)
|
|
||||||
}
|
|
||||||
log.Printf("Sent SetEP, entering SCSI command loop")
|
|
||||||
|
|
||||||
// Signal handling for clean unmount
|
|
||||||
sigCh := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
|
|
||||||
|
|
||||||
// SCSI command loop
|
|
||||||
dev := &SCSIDevice{
|
|
||||||
file: isoFile,
|
|
||||||
totalSectors: totalSectors,
|
|
||||||
sense: initialSenseData(),
|
|
||||||
}
|
|
||||||
|
|
||||||
err = commandLoop(conn, dev, sigCh)
|
|
||||||
|
|
||||||
// Clean unmount
|
|
||||||
log.Printf("Sending unmount...")
|
|
||||||
conn.Write(buildUnmountPacket())
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("command loop: %v", err)
|
|
||||||
}
|
|
||||||
log.Printf("Unmounted cleanly")
|
|
||||||
}
|
|
||||||
87
mount/mount.go
Normal file
87
mount/mount.go
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
package mount
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config holds the parameters for a virtual media mount session.
|
||||||
|
type Config struct {
|
||||||
|
Host string
|
||||||
|
Port int
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
ISOPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run connects to the BMC, mounts the ISO, and serves SCSI commands
|
||||||
|
// until ctx is cancelled or an error occurs. Sends unmount on return.
|
||||||
|
func Run(ctx context.Context, cfg Config) error {
|
||||||
|
isoFile, err := os.Open(cfg.ISOPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("open ISO: %w", err)
|
||||||
|
}
|
||||||
|
defer isoFile.Close()
|
||||||
|
|
||||||
|
isoInfo, err := isoFile.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("stat ISO: %w", err)
|
||||||
|
}
|
||||||
|
isoSize := isoInfo.Size()
|
||||||
|
if isoSize < 0x8054 {
|
||||||
|
return fmt.Errorf("ISO file too small (%d bytes)", isoSize)
|
||||||
|
}
|
||||||
|
totalSectors := uint32(isoSize / 2048)
|
||||||
|
log.Printf("ISO: %s (%d bytes, %d sectors)", cfg.ISOPath, isoSize, totalSectors)
|
||||||
|
|
||||||
|
addr := net.JoinHostPort(cfg.Host, fmt.Sprintf("%d", cfg.Port))
|
||||||
|
log.Printf("Connecting to %s...", addr)
|
||||||
|
|
||||||
|
var d net.Dialer
|
||||||
|
d.Timeout = 10 * time.Second
|
||||||
|
conn, err := d.DialContext(ctx, "tcp", addr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("connect: %w", err)
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
log.Printf("Connected")
|
||||||
|
|
||||||
|
if _, err := conn.Write(buildPlugInPacket(cfg.Username, cfg.Password)); err != nil {
|
||||||
|
return fmt.Errorf("send plugin: %w", err)
|
||||||
|
}
|
||||||
|
log.Printf("Sent PlugIn packet")
|
||||||
|
|
||||||
|
status, err := readMountStatus(conn)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("mount status: %w", err)
|
||||||
|
}
|
||||||
|
if status != 0x00 {
|
||||||
|
return fmt.Errorf("mount failed: %s", mountStatusString(status))
|
||||||
|
}
|
||||||
|
log.Printf("Mount OK")
|
||||||
|
|
||||||
|
if _, err := conn.Write(buildSetEPPacket()); err != nil {
|
||||||
|
return fmt.Errorf("send setep: %w", err)
|
||||||
|
}
|
||||||
|
log.Printf("Sent SetEP, entering SCSI command loop")
|
||||||
|
|
||||||
|
dev := &scsiDevice{
|
||||||
|
file: isoFile,
|
||||||
|
totalSectors: totalSectors,
|
||||||
|
sense: initialSenseData(),
|
||||||
|
}
|
||||||
|
|
||||||
|
err = commandLoop(ctx, conn, dev)
|
||||||
|
|
||||||
|
log.Printf("Sending unmount...")
|
||||||
|
conn.Write(buildUnmountPacket())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("command loop: %w", err)
|
||||||
|
}
|
||||||
|
log.Printf("Unmounted cleanly")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
package main
|
package mount
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/rc4"
|
"crypto/rc4"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -272,7 +272,7 @@ func buildCSW(ep byte, tag [4]byte, dataTransferLen uint32, actualLen uint32, st
|
||||||
pdu[3] = 0xFF
|
pdu[3] = 0xFF
|
||||||
binary.LittleEndian.PutUint32(pdu[4:8], 13)
|
binary.LittleEndian.PutUint32(pdu[4:8], 13)
|
||||||
// CSW body
|
// CSW body
|
||||||
pdu[8] = 'U' // dCSWSignature "USBS"
|
pdu[8] = 'U' // dCSWSignature "USBS"
|
||||||
pdu[9] = 'S'
|
pdu[9] = 'S'
|
||||||
pdu[10] = 'B'
|
pdu[10] = 'B'
|
||||||
pdu[11] = 'S'
|
pdu[11] = 'S'
|
||||||
|
|
@ -282,7 +282,7 @@ func buildCSW(ep byte, tag [4]byte, dataTransferLen uint32, actualLen uint32, st
|
||||||
residue = dataTransferLen - actualLen
|
residue = dataTransferLen - actualLen
|
||||||
}
|
}
|
||||||
binary.LittleEndian.PutUint32(pdu[16:20], residue) // dCSWDataResidue
|
binary.LittleEndian.PutUint32(pdu[16:20], residue) // dCSWDataResidue
|
||||||
pdu[20] = status // bCSWStatus
|
pdu[20] = status // bCSWStatus
|
||||||
return pdu
|
return pdu
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -307,22 +307,20 @@ func (h pduHeader) pduType() uint32 {
|
||||||
return binary.BigEndian.Uint32(h.typeOrTag[:])
|
return binary.BigEndian.Uint32(h.typeOrTag[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
// commandLoop reads PDU headers and dispatches commands until signal or error.
|
// commandLoop reads PDU headers and dispatches commands until context
|
||||||
func commandLoop(conn net.Conn, dev *SCSIDevice, sigCh <-chan os.Signal) error {
|
// cancellation or error.
|
||||||
|
func commandLoop(ctx context.Context, conn net.Conn, dev *scsiDevice) error {
|
||||||
for {
|
for {
|
||||||
// Check for signal (non-blocking)
|
if err := ctx.Err(); err != nil {
|
||||||
select {
|
|
||||||
case <-sigCh:
|
|
||||||
return nil
|
return nil
|
||||||
default:
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set read deadline so we can check signals periodically
|
// Set read deadline so we can check context periodically
|
||||||
conn.SetReadDeadline(time.Now().Add(1 * time.Second))
|
conn.SetReadDeadline(time.Now().Add(1 * time.Second))
|
||||||
hdr, err := readPDUHeader(conn)
|
hdr, err := readPDUHeader(conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
|
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
|
||||||
continue // timeout, loop back to check signal
|
continue // timeout, loop back to check context
|
||||||
}
|
}
|
||||||
return fmt.Errorf("read PDU header: %w", err)
|
return fmt.Errorf("read PDU header: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package mount
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package mount
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
|
@ -8,25 +8,25 @@ import (
|
||||||
|
|
||||||
// SCSI opcodes
|
// SCSI opcodes
|
||||||
const (
|
const (
|
||||||
scsiTestUnitReady = 0x00
|
scsiTestUnitReady = 0x00
|
||||||
scsiRequestSense = 0x03
|
scsiRequestSense = 0x03
|
||||||
scsiInquiry = 0x12
|
scsiInquiry = 0x12
|
||||||
scsiStartStopUnit = 0x1B
|
scsiStartStopUnit = 0x1B
|
||||||
scsiMediumRemoval = 0x1E
|
scsiMediumRemoval = 0x1E
|
||||||
scsiReadFormatCapacities = 0x23
|
scsiReadFormatCapacities = 0x23
|
||||||
scsiReadCapacity = 0x25
|
scsiReadCapacity = 0x25
|
||||||
scsiRead10 = 0x28
|
scsiRead10 = 0x28
|
||||||
scsiReadToc = 0x43
|
scsiReadToc = 0x43
|
||||||
scsiGetConfiguration = 0x46
|
scsiGetConfiguration = 0x46
|
||||||
scsiGetEventStatusNotif = 0x4A
|
scsiGetEventStatusNotif = 0x4A
|
||||||
scsiModeSense10 = 0x5A
|
scsiModeSense10 = 0x5A
|
||||||
scsiRead12 = 0xA8
|
scsiRead12 = 0xA8
|
||||||
)
|
)
|
||||||
|
|
||||||
// CSW status
|
// CSW status
|
||||||
const (
|
const (
|
||||||
cswStatusPassed = 0x00
|
cswStatusPassed = 0x00
|
||||||
cswStatusFailed = 0x01
|
cswStatusFailed = 0x01
|
||||||
)
|
)
|
||||||
|
|
||||||
// Sense keys
|
// Sense keys
|
||||||
|
|
@ -37,8 +37,8 @@ const (
|
||||||
senseIllegalRequest = 0x05
|
senseIllegalRequest = 0x05
|
||||||
)
|
)
|
||||||
|
|
||||||
// SCSIDevice holds state for the virtual CD-ROM.
|
// scsiDevice holds state for the virtual CD-ROM.
|
||||||
type SCSIDevice struct {
|
type scsiDevice struct {
|
||||||
file *os.File
|
file *os.File
|
||||||
totalSectors uint32
|
totalSectors uint32
|
||||||
sense [18]byte
|
sense [18]byte
|
||||||
|
|
@ -46,25 +46,25 @@ type SCSIDevice struct {
|
||||||
|
|
||||||
func initialSenseData() [18]byte {
|
func initialSenseData() [18]byte {
|
||||||
var s [18]byte
|
var s [18]byte
|
||||||
s[0] = 0x70 // Response code (current errors)
|
s[0] = 0x70 // Response code (current errors)
|
||||||
s[7] = 0x0A // Additional sense length
|
s[7] = 0x0A // Additional sense length
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *SCSIDevice) setSense(key, asc, ascq byte) {
|
func (d *scsiDevice) setSense(key, asc, ascq byte) {
|
||||||
d.sense = initialSenseData()
|
d.sense = initialSenseData()
|
||||||
d.sense[2] = key
|
d.sense[2] = key
|
||||||
d.sense[12] = asc
|
d.sense[12] = asc
|
||||||
d.sense[13] = ascq
|
d.sense[13] = ascq
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *SCSIDevice) setOK() {
|
func (d *scsiDevice) setOK() {
|
||||||
d.sense = initialSenseData()
|
d.sense = initialSenseData()
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleCBW parses a 31-byte CBW and returns the complete TCP response
|
// handleCBW parses a 31-byte CBW and returns the complete TCP response
|
||||||
// (data PDU + CSW, or just CSW).
|
// (data PDU + CSW, or just CSW).
|
||||||
func (d *SCSIDevice) handleCBW(ep byte, cbw []byte) []byte {
|
func (d *scsiDevice) handleCBW(ep byte, cbw []byte) []byte {
|
||||||
// Parse CBW fields (offsets relative to 31-byte CBW body)
|
// Parse CBW fields (offsets relative to 31-byte CBW body)
|
||||||
// CBW signature at 0-3 ("USBC")
|
// CBW signature at 0-3 ("USBC")
|
||||||
var tag [4]byte
|
var tag [4]byte
|
||||||
|
|
@ -96,7 +96,7 @@ func (d *SCSIDevice) handleCBW(ep byte, cbw []byte) []byte {
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *SCSIDevice) executeSCSI(cdb []byte, transferLen uint32) (data []byte, status byte) {
|
func (d *scsiDevice) executeSCSI(cdb []byte, transferLen uint32) (data []byte, status byte) {
|
||||||
if len(cdb) == 0 {
|
if len(cdb) == 0 {
|
||||||
d.setSense(senseIllegalRequest, 0x20, 0x00) // Invalid command
|
d.setSense(senseIllegalRequest, 0x20, 0x00) // Invalid command
|
||||||
return nil, cswStatusFailed
|
return nil, cswStatusFailed
|
||||||
|
|
@ -137,12 +137,12 @@ func (d *SCSIDevice) executeSCSI(cdb []byte, transferLen uint32) (data []byte, s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *SCSIDevice) cmdTestUnitReady() ([]byte, byte) {
|
func (d *scsiDevice) cmdTestUnitReady() ([]byte, byte) {
|
||||||
d.setOK()
|
d.setOK()
|
||||||
return nil, cswStatusPassed
|
return nil, cswStatusPassed
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *SCSIDevice) cmdRequestSense(cdb []byte) ([]byte, byte) {
|
func (d *scsiDevice) cmdRequestSense(cdb []byte) ([]byte, byte) {
|
||||||
allocLen := int(cdb[4])
|
allocLen := int(cdb[4])
|
||||||
resp := make([]byte, 18)
|
resp := make([]byte, 18)
|
||||||
copy(resp, d.sense[:])
|
copy(resp, d.sense[:])
|
||||||
|
|
@ -153,7 +153,7 @@ func (d *SCSIDevice) cmdRequestSense(cdb []byte) ([]byte, byte) {
|
||||||
return resp, cswStatusPassed
|
return resp, cswStatusPassed
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *SCSIDevice) cmdInquiry(cdb []byte) ([]byte, byte) {
|
func (d *scsiDevice) cmdInquiry(cdb []byte) ([]byte, byte) {
|
||||||
evpd := cdb[1] & 0x01
|
evpd := cdb[1] & 0x01
|
||||||
pageCode := cdb[2]
|
pageCode := cdb[2]
|
||||||
allocLen := int(cdb[3])<<8 | int(cdb[4])
|
allocLen := int(cdb[3])<<8 | int(cdb[4])
|
||||||
|
|
@ -216,17 +216,17 @@ func (d *SCSIDevice) cmdInquiry(cdb []byte) ([]byte, byte) {
|
||||||
return nil, cswStatusFailed
|
return nil, cswStatusFailed
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *SCSIDevice) cmdStartStopUnit() ([]byte, byte) {
|
func (d *scsiDevice) cmdStartStopUnit() ([]byte, byte) {
|
||||||
d.setOK()
|
d.setOK()
|
||||||
return nil, cswStatusPassed
|
return nil, cswStatusPassed
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *SCSIDevice) cmdMediumRemoval() ([]byte, byte) {
|
func (d *scsiDevice) cmdMediumRemoval() ([]byte, byte) {
|
||||||
d.setOK()
|
d.setOK()
|
||||||
return nil, cswStatusPassed
|
return nil, cswStatusPassed
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *SCSIDevice) cmdReadCapacity(cdb []byte) ([]byte, byte) {
|
func (d *scsiDevice) cmdReadCapacity(cdb []byte) ([]byte, byte) {
|
||||||
// Validate reserved bytes 1-8 are zero (except byte 2 which is OK)
|
// Validate reserved bytes 1-8 are zero (except byte 2 which is OK)
|
||||||
for i := 1; i <= 8; i++ {
|
for i := 1; i <= 8; i++ {
|
||||||
if i >= 2 && i <= 5 {
|
if i >= 2 && i <= 5 {
|
||||||
|
|
@ -246,7 +246,7 @@ func (d *SCSIDevice) cmdReadCapacity(cdb []byte) ([]byte, byte) {
|
||||||
return resp, cswStatusPassed
|
return resp, cswStatusPassed
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *SCSIDevice) cmdReadFormatCapacities(cdb []byte) ([]byte, byte) {
|
func (d *scsiDevice) cmdReadFormatCapacities(cdb []byte) ([]byte, byte) {
|
||||||
allocLen := int(cdb[7])<<8 | int(cdb[8])
|
allocLen := int(cdb[7])<<8 | int(cdb[8])
|
||||||
resp := make([]byte, 12)
|
resp := make([]byte, 12)
|
||||||
resp[3] = 0x08 // Capacity list length
|
resp[3] = 0x08 // Capacity list length
|
||||||
|
|
@ -263,7 +263,7 @@ func (d *SCSIDevice) cmdReadFormatCapacities(cdb []byte) ([]byte, byte) {
|
||||||
return resp, cswStatusPassed
|
return resp, cswStatusPassed
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *SCSIDevice) cmdRead10(cdb []byte) ([]byte, byte) {
|
func (d *scsiDevice) cmdRead10(cdb []byte) ([]byte, byte) {
|
||||||
lba := binary.BigEndian.Uint32(cdb[2:6])
|
lba := binary.BigEndian.Uint32(cdb[2:6])
|
||||||
count := uint32(cdb[7])<<8 | uint32(cdb[8])
|
count := uint32(cdb[7])<<8 | uint32(cdb[8])
|
||||||
|
|
||||||
|
|
@ -286,7 +286,7 @@ func (d *SCSIDevice) cmdRead10(cdb []byte) ([]byte, byte) {
|
||||||
return data[:n], cswStatusPassed
|
return data[:n], cswStatusPassed
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *SCSIDevice) cmdRead12(cdb []byte) ([]byte, byte) {
|
func (d *scsiDevice) cmdRead12(cdb []byte) ([]byte, byte) {
|
||||||
lba := binary.BigEndian.Uint32(cdb[2:6])
|
lba := binary.BigEndian.Uint32(cdb[2:6])
|
||||||
count := binary.BigEndian.Uint32(cdb[6:10])
|
count := binary.BigEndian.Uint32(cdb[6:10])
|
||||||
|
|
||||||
|
|
@ -309,7 +309,7 @@ func (d *SCSIDevice) cmdRead12(cdb []byte) ([]byte, byte) {
|
||||||
return data[:n], cswStatusPassed
|
return data[:n], cswStatusPassed
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *SCSIDevice) cmdReadToc(cdb []byte) ([]byte, byte) {
|
func (d *scsiDevice) cmdReadToc(cdb []byte) ([]byte, byte) {
|
||||||
format := cdb[2] & 0x0F
|
format := cdb[2] & 0x0F
|
||||||
if format != 0 {
|
if format != 0 {
|
||||||
d.setSense(senseIllegalRequest, 0x24, 0x00)
|
d.setSense(senseIllegalRequest, 0x24, 0x00)
|
||||||
|
|
@ -354,7 +354,7 @@ func (d *SCSIDevice) cmdReadToc(cdb []byte) ([]byte, byte) {
|
||||||
return resp, cswStatusPassed
|
return resp, cswStatusPassed
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *SCSIDevice) cmdGetConfiguration(cdb []byte) ([]byte, byte) {
|
func (d *scsiDevice) cmdGetConfiguration(cdb []byte) ([]byte, byte) {
|
||||||
allocLen := int(cdb[7])<<8 | int(cdb[8])
|
allocLen := int(cdb[7])<<8 | int(cdb[8])
|
||||||
if allocLen == 0 {
|
if allocLen == 0 {
|
||||||
d.setSense(senseIllegalRequest, 0x24, 0x00)
|
d.setSense(senseIllegalRequest, 0x24, 0x00)
|
||||||
|
|
@ -398,7 +398,7 @@ func (d *SCSIDevice) cmdGetConfiguration(cdb []byte) ([]byte, byte) {
|
||||||
return resp, cswStatusPassed
|
return resp, cswStatusPassed
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *SCSIDevice) cmdGetEventStatus(cdb []byte) ([]byte, byte) {
|
func (d *scsiDevice) cmdGetEventStatus(cdb []byte) ([]byte, byte) {
|
||||||
if cdb[1]&0x01 == 0 {
|
if cdb[1]&0x01 == 0 {
|
||||||
// Polled bit not set - async not supported
|
// Polled bit not set - async not supported
|
||||||
d.setSense(senseIllegalRequest, 0x24, 0x00)
|
d.setSense(senseIllegalRequest, 0x24, 0x00)
|
||||||
|
|
@ -424,7 +424,7 @@ func (d *SCSIDevice) cmdGetEventStatus(cdb []byte) ([]byte, byte) {
|
||||||
return resp, cswStatusPassed
|
return resp, cswStatusPassed
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *SCSIDevice) cmdModeSense10(cdb []byte) ([]byte, byte) {
|
func (d *scsiDevice) cmdModeSense10(cdb []byte) ([]byte, byte) {
|
||||||
pageCode := cdb[2] & 0x3F
|
pageCode := cdb[2] & 0x3F
|
||||||
allocLen := int(cdb[7])<<8 | int(cdb[8])
|
allocLen := int(cdb[7])<<8 | int(cdb[8])
|
||||||
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package mount
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
|
@ -6,16 +6,16 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newTestDevice(t *testing.T) *SCSIDevice {
|
func newTestDevice(t *testing.T) *scsiDevice {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
return &SCSIDevice{
|
return &scsiDevice{
|
||||||
file: nil, // No file for most tests
|
file: nil, // No file for most tests
|
||||||
totalSectors: 1000,
|
totalSectors: 1000,
|
||||||
sense: initialSenseData(),
|
sense: initialSenseData(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTestDeviceWithFile(t *testing.T) (*SCSIDevice, func()) {
|
func newTestDeviceWithFile(t *testing.T) (*scsiDevice, func()) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
// Create a minimal fake ISO (just enough for reads)
|
// Create a minimal fake ISO (just enough for reads)
|
||||||
f, err := os.CreateTemp("", "test-*.iso")
|
f, err := os.CreateTemp("", "test-*.iso")
|
||||||
|
|
@ -30,7 +30,7 @@ func newTestDeviceWithFile(t *testing.T) (*SCSIDevice, func()) {
|
||||||
f.Write(data)
|
f.Write(data)
|
||||||
f.Sync()
|
f.Sync()
|
||||||
|
|
||||||
dev := &SCSIDevice{
|
dev := &scsiDevice{
|
||||||
file: f,
|
file: f,
|
||||||
totalSectors: 10,
|
totalSectors: 10,
|
||||||
sense: initialSenseData(),
|
sense: initialSenseData(),
|
||||||
|
|
@ -173,14 +173,14 @@ func TestHandleCBW(t *testing.T) {
|
||||||
dev := newTestDevice(t)
|
dev := newTestDevice(t)
|
||||||
// Build a CBW for INQUIRY
|
// Build a CBW for INQUIRY
|
||||||
cbw := make([]byte, 31)
|
cbw := make([]byte, 31)
|
||||||
copy(cbw[0:4], []byte("USBC")) // Signature
|
copy(cbw[0:4], []byte("USBC")) // Signature
|
||||||
copy(cbw[4:8], []byte{1, 2, 3, 4}) // Tag
|
copy(cbw[4:8], []byte{1, 2, 3, 4}) // Tag
|
||||||
binary.LittleEndian.PutUint32(cbw[8:12], 36) // Transfer length
|
binary.LittleEndian.PutUint32(cbw[8:12], 36) // Transfer length
|
||||||
cbw[12] = 0x80 // Flags: IN
|
cbw[12] = 0x80 // Flags: IN
|
||||||
cbw[14] = 6 // CDB length
|
cbw[14] = 6 // CDB length
|
||||||
cbw[15] = 0x12 // INQUIRY opcode
|
cbw[15] = 0x12 // INQUIRY opcode
|
||||||
cbw[18] = 0x00 // Alloc len MSB (CDB byte 3)
|
cbw[18] = 0x00 // Alloc len MSB (CDB byte 3)
|
||||||
cbw[19] = 0x24 // Alloc len LSB (CDB byte 4) = 36
|
cbw[19] = 0x24 // Alloc len LSB (CDB byte 4) = 36
|
||||||
|
|
||||||
resp := dev.handleCBW(0x20, cbw)
|
resp := dev.handleCBW(0x20, cbw)
|
||||||
// Should have: 8 data PDU tag + 36 data + 8 CSW tag + 13 CSW body = 65
|
// Should have: 8 data PDU tag + 36 data + 8 CSW tag + 13 CSW body = 65
|
||||||
Loading…
Add table
Add a link
Reference in a new issue