feat: init

This commit is contained in:
Samuel N Cui
2022-09-06 12:39:08 +08:00
parent ba12af67da
commit cbcc6cc575
19 changed files with 1580 additions and 0 deletions

1
.gitignore vendored
View File

@@ -13,3 +13,4 @@
# Dependency directories (remove the comment below to include it)
# vendor/
output/

78
block.go Normal file
View File

@@ -0,0 +1,78 @@
package tapewriter
import (
"io"
"os"
"sync"
"syscall"
)
var (
_ = io.WriteCloser(new(BlockWriter))
)
type BlockWriter struct {
target uintptr
blockSize int
buffer chan []byte
pool sync.Pool
closed sync.WaitGroup
current []byte
off int
}
func NewBlockWriter(tape *os.File, blockSize, bufferBlocks int) *BlockWriter {
w := &BlockWriter{
target: tape.Fd(),
blockSize: blockSize,
buffer: make(chan []byte, bufferBlocks),
current: make([]byte, blockSize),
pool: sync.Pool{New: func() interface{} { return make([]byte, blockSize) }},
}
w.closed.Add(1)
go w.loop()
return w
}
func (w *BlockWriter) Write(buf []byte) (int, error) {
var n, cn int
for len(buf) > 0 {
cn = copy(w.current, buf)
buf = buf[cn:]
w.off += cn
n += cn
if w.off >= w.blockSize {
w.buffer <- w.current
w.current = w.pool.Get().([]byte)
}
}
return n, nil
}
func (w *BlockWriter) Close() error {
w.buffer <- w.current[:w.off]
close(w.buffer)
w.closed.Wait()
return nil
}
func (w *BlockWriter) loop() {
defer w.closed.Done()
for {
buf, ok := <-w.buffer
if !ok {
break
}
_, err := syscall.Write(int(w.target), buf)
if err != nil {
panic(err)
}
}
}

324
cmd/ordercp/main.go Normal file
View File

@@ -0,0 +1,324 @@
package main
import (
"context"
"fmt"
"io"
"os"
"os/signal"
"strings"
"time"
"github.com/abc950309/tapewriter/mmap"
"github.com/schollz/progressbar/v3"
"github.com/sirupsen/logrus"
)
const (
unexpectFileMode = os.ModeType &^ os.ModeDir
batchSize = 1024 * 1024
)
func main() {
src, dst := os.Args[1], os.Args[2]
c, err := NewCopyer(dst, src)
if err != nil {
panic(err)
}
c.Run()
}
type Copyer struct {
bar *progressbar.ProgressBar
dst, src string
fromTape bool
num int64
files []*Job
errs []error
copyPipe chan *CopyJob
changePipe chan *Job
}
func NewCopyer(dst, src string) (*Copyer, error) {
dst, src = strings.TrimSpace(dst), strings.TrimSpace(src)
if dst == "" {
return nil, fmt.Errorf("dst not found")
}
if src == "" {
return nil, fmt.Errorf("src not found")
}
if dst[len(dst)-1] != '/' {
dst = dst + "/"
}
dstStat, err := os.Stat(dst)
if err != nil {
return nil, fmt.Errorf("dst path '%s', %w", dst, err)
}
if !dstStat.IsDir() {
return nil, fmt.Errorf("dst path is not a dir")
}
srcStat, err := os.Stat(src)
if err != nil {
return nil, fmt.Errorf("src path '%s', %w", src, err)
}
if srcStat.IsDir() && src[len(src)-1] != '/' {
src = src + "/"
}
c := &Copyer{
dst: dst, src: src,
copyPipe: make(chan *CopyJob, 32),
changePipe: make(chan *Job, 8),
}
c.walk("", true)
var total int64
for _, file := range c.files {
total += file.Size
}
c.bar = progressbar.DefaultBytes(total)
return c, nil
}
func (c *Copyer) Run() {
ctx, cancel := context.WithCancel(context.Background())
signals := make(chan os.Signal, 1)
signal.Notify(signals, os.Interrupt)
go func() {
for sig := range signals {
if sig != os.Interrupt {
continue
}
cancel()
}
}()
go func() {
for _, file := range c.files {
c.prepare(ctx, file)
select {
case <-ctx.Done():
close(c.copyPipe)
return
default:
}
}
close(c.copyPipe)
}()
go func() {
for copyer := range c.copyPipe {
if err := c.copy(ctx, copyer); err != nil {
c.ReportError(c.dst+copyer.Path, err)
if err := os.Remove(c.dst + copyer.Path); err != nil {
c.ReportError(c.dst+copyer.Path, fmt.Errorf("delete file with error fail, %w", err))
}
}
select {
case <-ctx.Done():
close(c.changePipe)
return
default:
}
}
close(c.changePipe)
}()
for file := range c.changePipe {
c.changeInfo(file)
}
}
func (c *Copyer) ReportError(file string, err error) {
logrus.Errorf("'%s', %s", file, err)
c.errs = append(c.errs, fmt.Errorf("'%s': %w", file, err))
}
func (c *Copyer) walk(path string, first bool) {
stat, err := os.Stat(c.src + path)
if err != nil {
c.ReportError(c.src+path, fmt.Errorf("walk get stat, %w", err))
return
}
job := NewJobFromFileInfo(path, stat)
if job.Mode&unexpectFileMode != 0 {
return
}
if !job.Mode.IsDir() {
c.num++
job.Number = c.num
c.files = append(c.files, job)
return
}
if first {
files, err := os.ReadDir(c.src + path)
if err != nil {
c.ReportError(c.src+path, fmt.Errorf("walk read dir, %w", err))
return
}
for _, file := range files {
c.walk(file.Name(), false)
}
return
}
enterJob := new(Job)
*enterJob = *job
enterJob.Type = JobTypeEnterDir
c.files = append(c.files, enterJob)
files, err := os.ReadDir(c.src + path)
if err != nil {
c.ReportError(c.src+path, fmt.Errorf("walk read dir, %w", err))
return
}
for _, file := range files {
if first {
c.walk(file.Name(), false)
continue
}
c.walk(path+"/"+file.Name(), false)
}
exitJob := new(Job)
*exitJob = *job
exitJob.Type = JobTypeExitDir
c.files = append(c.files, exitJob)
}
func (c *Copyer) prepare(ctx context.Context, job *Job) {
switch job.Type {
case JobTypeEnterDir:
name := c.dst + job.Path
err := os.Mkdir(name, job.Mode&os.ModePerm)
if err != nil {
c.ReportError(name, fmt.Errorf("mkdir fail, %w", err))
return
}
return
case JobTypeExitDir:
c.copyPipe <- &CopyJob{Job: job}
return
}
name := c.src + job.Path
file, err := mmap.Open(name)
if err != nil {
c.ReportError(name, fmt.Errorf("open src file fail, %w", err))
return
}
c.copyPipe <- &CopyJob{Job: job, src: file}
}
func (c *Copyer) copy(ctx context.Context, job *CopyJob) error {
if job.src == nil {
c.changePipe <- job.Job
return nil
}
defer job.src.Close()
name := c.dst + job.Path
file, err := os.Create(name)
if err != nil {
return fmt.Errorf("open dst file fail, %w", err)
}
defer file.Close()
c.bar.Describe(fmt.Sprintf("[%d/%d]: %s", job.Number, c.num, job.Path))
if err := c.streamCopy(ctx, file, job.src); err != nil {
return fmt.Errorf("copy file fail, %w", err)
}
c.changePipe <- job.Job
return nil
}
func (c *Copyer) changeInfo(info *Job) {
name := c.dst + info.Path
if err := os.Chmod(name, info.Mode&os.ModePerm); err != nil {
c.ReportError(name, fmt.Errorf("change info, chmod fail, %w", err))
}
if err := os.Chtimes(name, info.ModTime, info.ModTime); err != nil {
c.ReportError(name, fmt.Errorf("change info, chtimes fail, %w", err))
}
}
func (c *Copyer) streamCopy(ctx context.Context, dst io.Writer, src *mmap.ReaderAt) error {
for idx := int64(0); ; idx += batchSize {
buf, err := src.Slice(idx, batchSize)
if err != nil {
return fmt.Errorf("slice mmap fail, %w", err)
}
nr := len(buf)
nw, ew := dst.Write(buf)
if nw < 0 || nr < nw {
nw = 0
if ew == nil {
return fmt.Errorf("write fail, unexpected return, byte_num= %d", nw)
}
return fmt.Errorf("write fail, %w", ew)
}
if nr != nw {
return fmt.Errorf("write fail, write and read bytes not equal, read= %d write= %d", nr, nw)
}
c.bar.Add(nr)
if len(buf) < batchSize {
return nil
}
select {
case <-ctx.Done():
return ctx.Err()
default:
}
}
}
type JobType uint8
const (
JobTypeNormal = JobType(iota)
JobTypeEnterDir
JobTypeExitDir
)
type Job struct {
Path string
Type JobType
Number int64
Name string // base name of the file
Size int64 // length in bytes for regular files; system-dependent for others
Mode os.FileMode // file mode bits
ModTime time.Time // modification time
}
func NewJobFromFileInfo(path string, info os.FileInfo) *Job {
job := &Job{
Path: path,
Name: info.Name(),
Size: info.Size(),
Mode: info.Mode(),
ModTime: info.ModTime(),
}
return job
}
type CopyJob struct {
*Job
src *mmap.ReaderAt
}

47
cmd/writer/main.go Normal file
View File

@@ -0,0 +1,47 @@
package main
import (
"archive/tar"
"fmt"
"io"
"os"
"github.com/abc950309/tapewriter"
)
func main() {
f, err := os.OpenFile("/dev/st0", os.O_WRONLY, 0666)
if err != nil {
panic(err)
}
w, err := tapewriter.NewWriter(f)
if err != nil {
panic(err)
}
path := os.Args[1]
info, err := os.Stat(path)
if err != nil {
panic(err)
}
target, err := os.Open(path)
if err != nil {
panic(err)
}
w.WriteHeader(&tar.Header{
Name: info.Name(),
Size: info.Size(),
})
// syscall.Write()
written, err := io.Copy(w, target)
if err != nil {
panic(err)
}
fmt.Println(written)
}

184
consts.go Normal file
View File

@@ -0,0 +1,184 @@
package tapewriter
const (
SSCCodeAllowOverwrite = 0x82
SSCCodeDisplayMessage = 0xC0
SSCCodeErase = 0x19
SSCCodeFormatMedium = 0x04
SSCCodeLoadUnload = 0x1B
SSCCodeLocate10 = 0x2B
SSCCodeLocate16 = 0x92
SSCCodePreventAllowMediumRemoval = 0x1E
SSCCodeRead = 0x08
SSCCodeReadBlockLimits = 0x05
SSCCodeReadDynamicRuntimeAttribute = 0xD1
SSCCodeReadPosition = 0x34
SSCCodeReadReverse = 0x0F
SSCCodeRecoverBufferedData = 0x14
SSCCodeReportDensitySupport = 0x44
SSCCodeRewind = 0x01
SSCCodeSetCapacity = 0x0B
SSCCodeSpace6 = 0x11
SSCCodeSpace16 = 0x91
SSCCodeStringSearch = 0xE3
SSCCodeVerify = 0x13
SSCCodeWrite = 0x0A
SSCCodeWriteDynamicRuntimeAttribute = 0xD2
SSCCodeWriteFilemarks6 = 0x10
)
const (
SPCCodeChangeDefinition = 0x40
SPCCodeXcopy = 0x83
SPCCodeInquiry = 0x12
SPCCodeLogSelect = 0x4C
SPCCodeLogSense = 0x4D
SPCCodeModeSelect6 = 0x15
SPCCodeModeSelect10 = 0x55
SPCCodeModeSense6 = 0x1A
SPCCodeModeSense10 = 0x5A
SPCCodePersistentReserveIn = 0x5E
SPCCodePersistentReserveOut = 0x5F
SPCCodeReadAttribute = 0x8C
SPCCodeReadBuffer = 0x3C
SPCCodeReceiveDiagnosticResults = 0x1C
SPCCodeReleaseUnit6 = 0x17
SPCCodeReleaseUnit10 = 0x57
SPCCodeReportLuns = 0xA0
SPCCodeRequestSense = 0x03
SPCCodeReserveUnit6 = 0x16
SPCCodeReserveUnit10 = 0x56
SPCCodeSpin = 0xA2
SPCCodeSpout = 0xB5
SPCCodeSendDiagnostic = 0x1D
SPCCodeTestUnitReady = 0x00
SPCCodeWriteAttribute = 0x8D
SPCCodeWriteBuffer = 0x3B
SPCCodeThirdPartyCopyIn = 0x84
SPCCodeMaintenanceIn = 0xA3
SPCCodeMaintenanceOut = 0xA4
)
// #define TEST_CRYPTO (0x20)
// #define MASK_CRYPTO (~0x20)
// typedef enum {
// TC_SPACE_EOD, /* Space EOD */
// TC_SPACE_FM_F, /* Space FM Forward */
// TC_SPACE_FM_B, /* Space FM Backword */
// TC_SPACE_F, /* Space Rec Forward */
// TC_SPACE_B, /* Space Rec Backword */
// } TC_SPACE_TYPE; /* Space command operations */
// typedef enum {
// TC_FORMAT_DEFAULT = 0x00, /* Make 1 partition medium */
// TC_FORMAT_PARTITION = 0x01, /* Make 2 partition medium */
// TC_FORMAT_DEST_PART = 0x02, /* Destroy all data and make 2 partition medium */
// TC_FORMAT_MAX = 0x03
// } TC_FORMAT_TYPE; /* Space command operations */
const (
FormatDefault = 0x00 // Make 1 partition medium
FormatPartition = 0x01 // Make 2 partition medium
FormatDestPart = 0x02 // Destroy all data and make 2 partition medium
FormatMax = 0x03
)
// typedef enum {
// TC_MP_PC_CURRENT = 0x00, /* Get current value */
// TC_MP_PC_CHANGEABLE = 0x40, /* Get changeable bitmap */
// TC_MP_PC_DEFAULT = 0x80, /* Get default(power-on) value */
// TC_MP_PC_SAVED = 0xC0, /* Get saved value */
// } TC_MP_PC_TYPE; /* Page control (PC) value for ModePage */
const (
TC_MP_PC_CURRENT = 0x00 /* Get current value */
TC_MP_PC_CHANGEABLE = 0x40 /* Get changeable bitmap */
TC_MP_PC_DEFAULT = 0x80 /* Get default(power-on) value */
TC_MP_PC_SAVED = 0xC0 /* Get saved value */
)
// #define TC_MP_DEV_CONFIG_EXT (0x10) // ModePage 0x10 (Device Configuration Extension Page)
// #define TC_MP_SUB_DEV_CONFIG_EXT (0x01) // ModePage SubPage 0x01 (Device Configuration Extension Page)
// #define TC_MP_DEV_CONFIG_EXT_SIZE (48)
// #define TC_MP_CTRL (0x0A) // ModePage 0x0A (Control Page)
// #define TC_MP_SUB_DP_CTRL (0xF0) // ModePage Subpage 0xF0 (Control Data Protection Page)
// #define TC_MP_SUB_DP_CTRL_SIZE (48)
// #define TC_MP_COMPRESSION (0x0F) // ModePage 0x0F (Data Compression Page)
// #define TC_MP_COMPRESSION_SIZE (32)
// #define TC_MP_MEDIUM_PARTITION (0x11) // ModePage 0x11 (Medium Partiton Page)
// #define TC_MP_MEDIUM_PARTITION_SIZE (28)
const (
TC_MP_MEDIUM_PARTITION = 0x11 // ModePage 0x11 (Medium Partiton Page)
TC_MP_MEDIUM_PARTITION_SIZE = 28
LOG_TAPECAPACITY = 0x31
LOG_TAPECAPACITY_SIZE = 32
)
// #define TC_MP_MEDIUM_SENSE (0x23) // ModePage 0x23 (Medium Sense Page)
// #define TC_MP_MEDIUM_SENSE_SIZE (76)
// #define TC_MP_INIT_EXT (0x24) // ModePage 0x24 (Initator-Specific Extentions)
// #define TC_MP_INIT_EXT_SIZE (40)
// #define TC_MP_READ_WRITE_CTRL (0x25) // ModePage 0x25 (Read/Write Control Page)
// #define TC_MP_READ_WRITE_CTRL_SIZE (48)
// #define TC_MP_SUPPORTEDPAGE (0x3F) // ModePage 0x3F (Supported Page Info)
// #define TC_MP_SUPPORTEDPAGE_SIZE (0xFF)
// #define TC_MAM_PAGE_HEADER_SIZE (0x5)
// #define TC_MAM_PAGE_VCR (0x0009) /* Page code of Volume Change Reference */
// #define TC_MAM_PAGE_VCR_SIZE (0x4) /* Size of Volume Change Reference */
// #define TC_MAM_PAGE_COHERENCY (0x080C)
// #define TC_MAM_PAGE_COHERENCY_SIZE (0x46)
// #define TC_MAM_APP_VENDER (0x0800)
// #define TC_MAM_APP_VENDER_SIZE (0x8)
// #define TC_MAM_APP_NAME (0x0801)
// #define TC_MAM_APP_NAME_SIZE (0x20)
// #define TC_MAM_APP_VERSION (0x0802)
// #define TC_MAM_APP_VERSION_SIZE (0x8)
// #define TC_MAM_USER_MEDIUM_LABEL (0x0803)
// #define TC_MAM_USER_MEDIUM_LABEL_SIZE (0xA0)
// #define TC_MAM_TEXT_LOCALIZATION_IDENTIFIER (0x0805)
// #define TC_MAM_TEXT_LOCALIZATION_IDENTIFIER_SIZE (0x1)
// #define TC_MAM_BARCODE (0x0806)
// #define TC_MAM_BARCODE_SIZE (0x20)
// #define TC_MAM_BARCODE_LEN TC_MAM_BARCODE_SIZE /* HPE LTFS alias */
// #define TC_MAM_MEDIA_POOL (0x0808)
// #define TC_MAM_MEDIA_POOL_SIZE (0xA0)
// #define TC_MAM_APP_FORMAT_VERSION (0x080B)
// #define TC_MAM_APP_FORMAT_VERSION_SIZE (0x10)
// #define TC_MAM_LOCKED_MAM (0x1623)
// #define TC_MAM_LOCKED_MAM_SIZE (0x1)
// #define BINARY_FORMAT (0x0)
// #define ASCII_FORMAT (0x1)
// #define TEXT_FORMAT (0x2)
// #define TEXT_LOCALIZATION_IDENTIFIER_ASCII (0x0)
// #define TEXT_LOCALIZATION_IDENTIFIER_UTF8 (0x81)
// #define TC_MAM_PAGE_ATTRIBUTE_ALL 0 /* Page code for all the attribute passed
// while formatting and mounting the volume */
// enum eod_status {
// EOD_GOOD = 0x00,
// EOD_MISSING = 0x01,
// EOD_UNKNOWN = 0x02
// };
// enum {
// MEDIUM_UNKNOWN = 0,
// MEDIUM_PERFECT_MATCH,
// MEDIUM_WRITABLE,
// MEDIUM_PROBABLY_WRITABLE,
// MEDIUM_READONLY,
// MEDIUM_CANNOT_ACCESS
// };

2
encrypttape Normal file
View File

@@ -0,0 +1,2 @@
# stenc -g 256 -k /root/tape.key -kd TapeMasterKey
stenc -f /dev/st0 -e on -k /root/tape.key -a 1 --ckod

19
go.mod Normal file
View File

@@ -0,0 +1,19 @@
module github.com/abc950309/tapewriter
go 1.17
require (
github.com/benmcclelland/mtio v0.0.0-20170506231306-f929531fb4fe
github.com/benmcclelland/sgio v0.0.0-20180629175614-f710aebf64c1
github.com/davecgh/go-spew v1.1.1
github.com/schollz/progressbar/v3 v3.10.1
github.com/sirupsen/logrus v1.9.0
)
require (
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/rivo/uniseg v0.3.4 // indirect
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 // indirect
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 // indirect
)

36
go.sum Normal file
View File

@@ -0,0 +1,36 @@
github.com/benmcclelland/mtio v0.0.0-20170506231306-f929531fb4fe h1:f+PTGRJrCYSquf31olVAWIqyJwx42eBzVH4D3igzgSk=
github.com/benmcclelland/mtio v0.0.0-20170506231306-f929531fb4fe/go.mod h1:XyVqnMjuqI1qOvgei81EgX68tV7BjN9JlluJPsjArs0=
github.com/benmcclelland/sgio v0.0.0-20180629175614-f710aebf64c1 h1:f1AIRyf6d21xBd1DirrIa6fk41O3LB0WvVuVqhPN4co=
github.com/benmcclelland/sgio v0.0.0-20180629175614-f710aebf64c1/go.mod h1:WdrapyVn/Aduwwf/OMW6sEtk9+7BSoMst1kGrx4E4xE=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.3.4 h1:3Z3Eu6FGHZWSfNKJTOUiPatWwfc7DzJRU04jFUqJODw=
github.com/rivo/uniseg v0.3.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/schollz/progressbar/v3 v3.10.1 h1:6A8v8TIcCJL4yemlUJS9gdcpZ++Gy6toOh1JzKQkz+U=
github.com/schollz/progressbar/v3 v3.10.1/go.mod h1:R2djRgv58sn00AGysc4fN0ip4piOGd3z88K+zVBjczs=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 h1:v6hYoSR9T5oet+pMXwUWkbiVqx/63mlHjefrHmxwfeY=
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 h1:Q5284mrmYTpACcm+eAKjKJH48BBwSyfJqmmGDTtT8Vc=
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

16
maketape Executable file
View File

@@ -0,0 +1,16 @@
#!/usr/bin/env bash
set -ex;
echo "format tape as number '$1', name '$2'"
echo "copy '$3' to tape"
stenc -f /dev/st0 -e on -k /root/tape.key -a 1 --ckod
mkltfs -f -d /dev/st0 -s $1 -n $2
ltfs -o noatime -o sync_type=unmount -o work_directory=/opt/ltfs -o capture_index -o min_pool_size=256 -o max_pool_size=1024 -o eject /ltfs
ordercp $3 /ltfs/
umount /ltfs
until mt -f /dev/st0 rewoffl; do
echo 'waiting for unmount write index...'
sleep 5
done

View File

@@ -0,0 +1,56 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build ignore
// +build ignore
//
// This build tag means that "go build" does not build this file. Use "go run
// manual_test_program.go" to run it.
//
// You will also need to change "debug = false" to "debug = true" in mmap_*.go.
package main
import (
"log"
"math/rand"
"time"
"golang.org/x/exp/mmap"
)
var garbage []byte
func main() {
const filename = "manual_test_program.go"
for _, explicitClose := range []bool{false, true} {
r, err := mmap.Open(filename)
if err != nil {
log.Fatalf("Open: %v", err)
}
if explicitClose {
r.Close()
} else {
// Leak the *mmap.ReaderAt returned by mmap.Open. The finalizer
// should pick it up, if finalizers run at all.
}
}
println("Finished all explicit Close calls.")
println("Creating and collecting garbage.")
println("Look for two munmap log messages.")
println("Hit Ctrl-C to exit.")
rng := rand.New(rand.NewSource(1))
now := time.Now()
for {
garbage = make([]byte, rng.Intn(1<<20))
if time.Since(now) > 1*time.Second {
now = time.Now()
print(".")
}
}
}

136
mmap/mmap_darwin.go Normal file
View File

@@ -0,0 +1,136 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build darwin
// +build darwin
// Package mmap provides a way to memory-map a file.
package mmap
import (
"errors"
"fmt"
"io"
"os"
"runtime"
"syscall"
)
// debug is whether to print debugging messages for manual testing.
//
// The runtime.SetFinalizer documentation says that, "The finalizer for x is
// scheduled to run at some arbitrary time after x becomes unreachable. There
// is no guarantee that finalizers will run before a program exits", so we
// cannot automatically test that the finalizer runs. Instead, set this to true
// when running the manual test.
const debug = false
// ReaderAt reads a memory-mapped file.
//
// Like any io.ReaderAt, clients can execute parallel ReadAt calls, but it is
// not safe to call Close and reading methods concurrently.
type ReaderAt struct {
data []byte
}
// Close closes the reader.
func (r *ReaderAt) Close() error {
if r.data == nil {
return nil
}
data := r.data
r.data = nil
if debug {
var p *byte
if len(data) != 0 {
p = &data[0]
}
println("munmap", r, p)
}
runtime.SetFinalizer(r, nil)
return syscall.Munmap(data)
}
// Len returns the length of the underlying memory-mapped file.
func (r *ReaderAt) Len() int {
return len(r.data)
}
// At returns the byte at index i.
func (r *ReaderAt) At(i int) byte {
return r.data[i]
}
// ReadAt implements the io.ReaderAt interface.
func (r *ReaderAt) ReadAt(p []byte, off int64) (int, error) {
if r.data == nil {
return 0, errors.New("mmap: closed")
}
if off < 0 || int64(len(r.data)) < off {
return 0, fmt.Errorf("mmap: invalid ReadAt offset %d", off)
}
n := copy(p, r.data[off:])
if n < len(p) {
return n, io.EOF
}
return n, nil
}
// ReadAt implements the io.ReaderAt interface.
func (r *ReaderAt) Slice(off, limit int64) ([]byte, error) {
if r.data == nil {
return nil, errors.New("mmap: closed")
}
l := int64(len(r.data))
if off < 0 || limit < 0 || l < off {
return nil, fmt.Errorf("mmap: invalid ReadAt offset %d", off)
}
if off+limit > l {
return r.data[off:], nil
}
return r.data[off : off+limit], nil
}
// Open memory-maps the named file for reading.
func Open(filename string) (*ReaderAt, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
return nil, err
}
size := fi.Size()
if size == 0 {
return &ReaderAt{}, nil
}
if size < 0 {
return nil, fmt.Errorf("mmap: file %q has negative size", filename)
}
if size != int64(int(size)) {
return nil, fmt.Errorf("mmap: file %q is too large", filename)
}
data, err := syscall.Mmap(int(f.Fd()), 0, int(size), syscall.PROT_READ, syscall.MAP_SHARED)
if err != nil {
return nil, fmt.Errorf("create mmap fail, %q, %w", filename, err)
}
r := &ReaderAt{data}
if debug {
var p *byte
if len(data) != 0 {
p = &data[0]
}
println("mmap", r, p)
}
runtime.SetFinalizer(r, (*ReaderAt).Close)
return r, nil
}

140
mmap/mmap_linux.go Normal file
View File

@@ -0,0 +1,140 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build linux
// +build linux
// Package mmap provides a way to memory-map a file.
package mmap
import (
"errors"
"fmt"
"io"
"log"
"os"
"runtime"
"syscall"
)
// debug is whether to print debugging messages for manual testing.
//
// The runtime.SetFinalizer documentation says that, "The finalizer for x is
// scheduled to run at some arbitrary time after x becomes unreachable. There
// is no guarantee that finalizers will run before a program exits", so we
// cannot automatically test that the finalizer runs. Instead, set this to true
// when running the manual test.
const debug = false
// ReaderAt reads a memory-mapped file.
//
// Like any io.ReaderAt, clients can execute parallel ReadAt calls, but it is
// not safe to call Close and reading methods concurrently.
type ReaderAt struct {
data []byte
}
// Close closes the reader.
func (r *ReaderAt) Close() error {
if r.data == nil {
return nil
}
data := r.data
r.data = nil
if debug {
var p *byte
if len(data) != 0 {
p = &data[0]
}
println("munmap", r, p)
}
runtime.SetFinalizer(r, nil)
return syscall.Munmap(data)
}
// Len returns the length of the underlying memory-mapped file.
func (r *ReaderAt) Len() int {
return len(r.data)
}
// At returns the byte at index i.
func (r *ReaderAt) At(i int) byte {
return r.data[i]
}
// ReadAt implements the io.ReaderAt interface.
func (r *ReaderAt) ReadAt(p []byte, off int64) (int, error) {
if r.data == nil {
return 0, errors.New("mmap: closed")
}
if off < 0 || int64(len(r.data)) < off {
return 0, fmt.Errorf("mmap: invalid ReadAt offset %d", off)
}
n := copy(p, r.data[off:])
if n < len(p) {
return n, io.EOF
}
return n, nil
}
// ReadAt implements the io.ReaderAt interface.
func (r *ReaderAt) Slice(off, limit int64) ([]byte, error) {
if r.data == nil {
return nil, errors.New("mmap: closed")
}
l := int64(len(r.data))
if off < 0 || limit < 0 || l < off {
return nil, fmt.Errorf("mmap: invalid ReadAt offset %d", off)
}
if off+limit > l {
return r.data[off:], nil
}
return r.data[off : off+limit], nil
}
// Open memory-maps the named file for reading.
func Open(filename string) (*ReaderAt, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
return nil, err
}
size := fi.Size()
if size == 0 {
return &ReaderAt{}, nil
}
if size < 0 {
return nil, fmt.Errorf("mmap: file %q has negative size", filename)
}
if size != int64(int(size)) {
return nil, fmt.Errorf("mmap: file %q is too large", filename)
}
data, err := syscall.Mmap(int(f.Fd()), 0, int(size), syscall.PROT_READ, syscall.MAP_SHARED)
if err != nil {
return nil, fmt.Errorf("create mmap fail, %q, %w", filename, err)
}
if err := syscall.Madvise(mem, syscall.MADV_SEQUENTIAL|syscall.MADV_WILLNEED); err != nil {
return nil, fmt.Errorf("madvise fail, %q, %w", filename, err)
}
r := &ReaderAt{data}
if debug {
var p *byte
if len(data) != 0 {
p = &data[0]
}
println("mmap", r, p)
}
runtime.SetFinalizer(r, (*ReaderAt).Close)
return r, nil
}

86
mmap/mmap_other.go Normal file
View File

@@ -0,0 +1,86 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !linux && !windows && !darwin
// +build !linux,!windows,!darwin
// Package mmap provides a way to memory-map a file.
package mmap
import (
"fmt"
"os"
)
// ReaderAt reads a memory-mapped file.
//
// Like any io.ReaderAt, clients can execute parallel ReadAt calls, but it is
// not safe to call Close and reading methods concurrently.
type ReaderAt struct {
f *os.File
len int
}
// Close closes the reader.
func (r *ReaderAt) Close() error {
return r.f.Close()
}
// Len returns the length of the underlying memory-mapped file.
func (r *ReaderAt) Len() int {
return r.len
}
// At returns the byte at index i.
func (r *ReaderAt) At(i int) byte {
if i < 0 || r.len <= i {
panic("index out of range")
}
var b [1]byte
r.ReadAt(b[:], int64(i))
return b[0]
}
// ReadAt implements the io.ReaderAt interface.
func (r *ReaderAt) ReadAt(p []byte, off int64) (int, error) {
return r.f.ReadAt(p, off)
}
// ReadAt implements the io.ReaderAt interface.
func (r *ReaderAt) Slice(off, limit int64) ([]byte, error) {
buf := make([]byte, limit)
n, err := r.ReadAt(buf, off)
if err != nil {
return nil, err
}
return buf[:n], nil
}
// Open memory-maps the named file for reading.
func Open(filename string) (*ReaderAt, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
fi, err := f.Stat()
if err != nil {
f.Close()
return nil, err
}
size := fi.Size()
if size < 0 {
f.Close()
return nil, fmt.Errorf("mmap: file %q has negative size", filename)
}
if size != int64(int(size)) {
f.Close()
return nil, fmt.Errorf("mmap: file %q is too large", filename)
}
return &ReaderAt{
f: f,
len: int(fi.Size()),
}, nil
}

34
mmap/mmap_test.go Normal file
View File

@@ -0,0 +1,34 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package mmap
import (
"bytes"
"io"
"io/ioutil"
"testing"
)
func TestOpen(t *testing.T) {
const filename = "mmap_test.go"
r, err := Open(filename)
if err != nil {
t.Fatalf("Open: %v", err)
}
got := make([]byte, r.Len())
if _, err := r.ReadAt(got, 0); err != nil && err != io.EOF {
t.Fatalf("ReadAt: %v", err)
}
want, err := ioutil.ReadFile(filename)
if err != nil {
t.Fatalf("ioutil.ReadFile: %v", err)
}
if len(got) != len(want) {
t.Fatalf("got %d bytes, want %d", len(got), len(want))
}
if !bytes.Equal(got, want) {
t.Fatalf("\ngot %q\nwant %q", string(got), string(want))
}
}

141
mmap/mmap_windows.go Normal file
View File

@@ -0,0 +1,141 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package mmap provides a way to memory-map a file.
package mmap
import (
"errors"
"fmt"
"io"
"os"
"runtime"
"syscall"
"unsafe"
)
// debug is whether to print debugging messages for manual testing.
//
// The runtime.SetFinalizer documentation says that, "The finalizer for x is
// scheduled to run at some arbitrary time after x becomes unreachable. There
// is no guarantee that finalizers will run before a program exits", so we
// cannot automatically test that the finalizer runs. Instead, set this to true
// when running the manual test.
const debug = false
// ReaderAt reads a memory-mapped file.
//
// Like any io.ReaderAt, clients can execute parallel ReadAt calls, but it is
// not safe to call Close and reading methods concurrently.
type ReaderAt struct {
data []byte
}
// Close closes the reader.
func (r *ReaderAt) Close() error {
if r.data == nil {
return nil
}
data := r.data
r.data = nil
if debug {
var p *byte
if len(data) != 0 {
p = &data[0]
}
println("munmap", r, p)
}
runtime.SetFinalizer(r, nil)
return syscall.UnmapViewOfFile(uintptr(unsafe.Pointer(&data[0])))
}
// Len returns the length of the underlying memory-mapped file.
func (r *ReaderAt) Len() int {
return len(r.data)
}
// At returns the byte at index i.
func (r *ReaderAt) At(i int) byte {
return r.data[i]
}
// ReadAt implements the io.ReaderAt interface.
func (r *ReaderAt) ReadAt(p []byte, off int64) (int, error) {
if r.data == nil {
return 0, errors.New("mmap: closed")
}
if off < 0 || int64(len(r.data)) < off {
return 0, fmt.Errorf("mmap: invalid ReadAt offset %d", off)
}
n := copy(p, r.data[off:])
if n < len(p) {
return n, io.EOF
}
return n, nil
}
// ReadAt implements the io.ReaderAt interface.
func (r *ReaderAt) Slice(off, limit int64) ([]byte, error) {
if r.data == nil {
return nil, errors.New("mmap: closed")
}
l := int64(len(r.data))
if off < 0 || limit < 0 || l < off {
return nil, fmt.Errorf("mmap: invalid ReadAt offset %d", off)
}
if off+limit > l {
return r.data[off:], nil
}
return r.data[off : off+limit], nil
}
// Open memory-maps the named file for reading.
func Open(filename string) (*ReaderAt, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
return nil, err
}
size := fi.Size()
if size == 0 {
return &ReaderAt{}, nil
}
if size < 0 {
return nil, fmt.Errorf("mmap: file %q has negative size", filename)
}
if size != int64(int(size)) {
return nil, fmt.Errorf("mmap: file %q is too large", filename)
}
low, high := uint32(size), uint32(size>>32)
fmap, err := syscall.CreateFileMapping(syscall.Handle(f.Fd()), nil, syscall.PAGE_READONLY, high, low, nil)
if err != nil {
return nil, err
}
defer syscall.CloseHandle(fmap)
ptr, err := syscall.MapViewOfFile(fmap, syscall.FILE_MAP_READ, 0, 0, uintptr(size))
if err != nil {
return nil, err
}
data := unsafe.Slice((*byte)(unsafe.Pointer(ptr)), size)
r := &ReaderAt{data: data}
if debug {
var p *byte
if len(data) != 0 {
p = &data[0]
}
println("mmap", r, p)
}
runtime.SetFinalizer(r, (*ReaderAt).Close)
return r, nil
}

1
sg_tools.go Normal file
View File

@@ -0,0 +1 @@
package tapewriter

2
test.sh Normal file
View File

@@ -0,0 +1,2 @@
GOOS=linux go build -o ./output/ordercp ./cmd/ordercp
scp ./output/ordercp tape:ordercp

34
tools.go Normal file
View File

@@ -0,0 +1,34 @@
package tapewriter
import "fmt"
const (
LOG_PAGE_HEADER_SIZE = 4
LOG_PAGE_PARAMSIZE_OFFSET = 3
LOG_PAGE_PARAM_OFFSET = 4
)
func parseLogPage(buf []byte) (map[uint16][]byte, error) {
pageLen := int(buf[2])<<8 + int(buf[3])
result := make(map[uint16][]byte, pageLen)
for i := LOG_PAGE_HEADER_SIZE; i < pageLen; {
key := uint16(buf[i])<<8 + uint16(buf[i+1])
valueLen := int(buf[i+LOG_PAGE_PARAMSIZE_OFFSET])
end := i + LOG_PAGE_PARAM_OFFSET + valueLen
if i+LOG_PAGE_PARAM_OFFSET+valueLen > len(buf) {
return nil, fmt.Errorf("log page format unexpected, value len overflow, has= %d max= %d", i+LOG_PAGE_PARAM_OFFSET+valueLen, len(buf))
}
value := buf[i+LOG_PAGE_PARAM_OFFSET : end]
copyed := make([]byte, len(value))
copy(copyed, value)
result[key] = copyed
i += valueLen + LOG_PAGE_PARAM_OFFSET
}
return result, nil
}

243
writer.go Normal file
View File

@@ -0,0 +1,243 @@
package tapewriter
import (
"archive/tar"
"encoding/binary"
"fmt"
"os"
"github.com/benmcclelland/mtio"
"github.com/benmcclelland/sgio"
"github.com/davecgh/go-spew/spew"
"github.com/sirupsen/logrus"
)
const (
MaxSense = 255
BlockSize = 512 * 1024
)
type position struct {
partition uint8
offset uint64
}
type capacity struct {
cap uint64
all uint64
}
type Writer struct {
*tar.Writer
buffer *BlockWriter
tape *os.File
index []*tar.Header
blockSize int
writen uint64
current *position
}
func NewWriter(tape *os.File) (*Writer, error) {
w := &Writer{
tape: tape,
index: make([]*tar.Header, 0, 16),
blockSize: BlockSize,
writen: 0,
current: new(position),
}
w.buffer = NewBlockWriter(w.tape, w.blockSize, 32)
w.Writer = tar.NewWriter(w.buffer)
if err := w.formatTape(); err != nil {
return nil, err
}
if err := mtio.DoOp(w.tape, mtio.NewMtOp(mtio.WithOperation(mtio.MTSETBLK), mtio.WithCount(BlockSize))); err != nil {
return nil, err
}
cap, err := w.readCapacity()
if err != nil {
return nil, err
}
spew.Dump(cap)
return w, nil
}
func (w *Writer) Close() error {
if err := w.Writer.Close(); err != nil {
return err
}
if err := w.buffer.Close(); err != nil {
return err
}
return nil
}
func (w *Writer) formatTape() error {
// mode sense -> mode select -> format
partitionMode := make([]byte, 32)
if err := w.modeSense(TC_MP_MEDIUM_PARTITION, TC_MP_PC_CURRENT, 0x00, partitionMode, TC_MP_MEDIUM_PARTITION_SIZE); err != nil {
return fmt.Errorf("read partition config fail, err= %w", err)
}
logrus.Infof("read partition mode success, '%x'", partitionMode)
// Set appropriate values to the page and Issue Mode Select
partitionMode[0] = 0x00
partitionMode[1] = 0x00
partitionMode[19] = 0x01
partitionMode[20] = 0x20 | (partitionMode[20] & 0x1F) /* Set FDP=0, SDP=0, IDP=1 ==> User Setting */
partitionMode[22] = 0x09 /* Set partition unit as gigabytes (10^9) */
partitionMode[24] = 0x00 /* Set Partition0 Capacity */
partitionMode[25] = 1 /* will round up to minimum partition size */
partitionMode[26] = 0xFF /* Set Partition1 Capacity */
partitionMode[27] = 0xFF
logrus.Infof("edit partition mode success, '%x'", partitionMode)
pageLength := uint16(TC_MP_MEDIUM_PARTITION_SIZE)
if partitionMode[17] > 0x0A {
pageLength += uint16(partitionMode[17] - 0x0A)
}
if err := w.modeSelect(partitionMode, pageLength); err != nil {
return fmt.Errorf("write partition mode fail, err= %w", err)
}
if err := w.formatPartition(); err != nil {
return fmt.Errorf("format partition fail, err= %w", err)
}
if err := w.locate(&position{partition: 1, offset: 0}); err != nil {
return fmt.Errorf("locate fail, err= %w", err)
}
return nil
}
// only for lto5
func (w *Writer) readCapacity() ([]*capacity, error) {
buf := make([]byte, 1024)
if err := w.logSense(LOG_TAPECAPACITY, 0, buf); err != nil {
return nil, fmt.Errorf("read capacity fail, err= %w", err)
}
page, err := parseLogPage(buf)
if err != nil {
return nil, fmt.Errorf("parse log page fail, err= %w", err)
}
result := make([]*capacity, 2)
for idx := range result {
cap := page[uint16(idx+1)]
all := page[uint16(len(result)+idx+1)]
c := new(capacity)
if len(cap) >= 4 {
c.cap = uint64(binary.BigEndian.Uint32(cap))
}
if len(cap) >= 4 {
c.all = uint64(binary.BigEndian.Uint32(all))
}
result[idx] = c
}
return result, nil
}
func (w *Writer) modeSense(page, pc, subpage uint8, buf []byte, size uint16) error {
cdb := make([]uint8, 10)
cdb[0] = SPCCodeModeSense10
cdb[2] = pc | (page & 0x3F) // Current value
cdb[3] = subpage
cdb[7] = uint8(size << 8)
cdb[8] = uint8(size)
return w.sendCmd(sgio.SG_DXFER_FROM_DEV, buf, cdb...)
}
func (w *Writer) modeSelect(buf []byte, size uint16) error {
cdb := make([]uint8, 10)
cdb[0] = SPCCodeModeSelect10
cdb[1] = 0x10
cdb[7] = uint8(size << 8)
cdb[8] = uint8(size)
return w.sendCmd(sgio.SG_DXFER_TO_DEV, buf, cdb...)
}
func (w *Writer) logSense(page, subpage uint8, buf []byte) error {
cdb := make([]uint8, 10)
cdb[0] = SPCCodeLogSense
cdb[2] = 0x40 | (page & 0x3F) // Current value
cdb[3] = subpage
cdb[7] = 0xff
cdb[8] = 0xff
resp := make([]byte, 0xffff)
if err := w.sendCmd(sgio.SG_DXFER_FROM_DEV, resp, cdb...); err != nil {
return fmt.Errorf("send cmd fail, err= %w", err)
}
copy(buf, resp)
return nil
}
func (w *Writer) formatPartition() error {
return w.sendCmd(sgio.SG_DXFER_TO_FROM_DEV, nil, SSCCodeFormatMedium, 0, FormatDestPart, 0, 0, 0)
}
func (w *Writer) locate(target *position) error {
cdb := make([]uint8, 16)
cdb[0] = SSCCodeLocate16
if w.current.partition != target.partition {
cdb[1] = 0x02 // Set Change partition(CP) flag
}
cdb[2] = target.partition
blockNum := target.offset / uint64(w.blockSize)
binary.BigEndian.PutUint64(cdb[4:], blockNum)
if err := w.sendCmd(sgio.SG_DXFER_TO_FROM_DEV, nil, SSCCodeFormatMedium, 0, FormatDestPart, 0, 0, 0); err != nil {
return fmt.Errorf("send locate cmd fail, err= %w", err)
}
// left := int(target.offset) % int(w.blockSize)
return nil
}
func (w *Writer) sendCmd(direction int32, dxfer []byte, cmd ...uint8) error {
senseBuf := make([]byte, MaxSense)
ioHdr := &sgio.SgIoHdr{
InterfaceID: int32('S'),
DxferDirection: direction,
CmdLen: uint8(len(cmd)),
Cmdp: &cmd[0],
MxSbLen: uint8(len(senseBuf)),
Sbp: &senseBuf[0],
// DxferLen: 0,
// Dxferp: ,
Timeout: sgio.TIMEOUT_20_SECS,
}
if len(dxfer) > 0 {
ioHdr.DxferLen = uint32(len(dxfer))
ioHdr.Dxferp = &dxfer[0]
}
if err := sgio.SgioSyscall(w.tape, ioHdr); err != nil {
return err
}
if err := sgio.CheckSense(ioHdr, &senseBuf); err != nil {
return err
}
return nil
}