diff --git a/.gitignore b/.gitignore index 66fd13c..d5a7e4b 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ # Dependency directories (remove the comment below to include it) # vendor/ +output/ diff --git a/block.go b/block.go new file mode 100644 index 0000000..cc46c71 --- /dev/null +++ b/block.go @@ -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) + } + } +} diff --git a/cmd/ordercp/main.go b/cmd/ordercp/main.go new file mode 100644 index 0000000..3879e72 --- /dev/null +++ b/cmd/ordercp/main.go @@ -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 +} diff --git a/cmd/writer/main.go b/cmd/writer/main.go new file mode 100644 index 0000000..b850971 --- /dev/null +++ b/cmd/writer/main.go @@ -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) +} diff --git a/consts.go b/consts.go new file mode 100644 index 0000000..2f8d17c --- /dev/null +++ b/consts.go @@ -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 +// }; diff --git a/encrypttape b/encrypttape new file mode 100644 index 0000000..3d1f88c --- /dev/null +++ b/encrypttape @@ -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 diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..7429beb --- /dev/null +++ b/go.mod @@ -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 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..fe17db3 --- /dev/null +++ b/go.sum @@ -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= diff --git a/maketape b/maketape new file mode 100755 index 0000000..1672d49 --- /dev/null +++ b/maketape @@ -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 diff --git a/mmap/manual_test_program.go b/mmap/manual_test_program.go new file mode 100644 index 0000000..a1ab17b --- /dev/null +++ b/mmap/manual_test_program.go @@ -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(".") + } + } +} diff --git a/mmap/mmap_darwin.go b/mmap/mmap_darwin.go new file mode 100644 index 0000000..1882efb --- /dev/null +++ b/mmap/mmap_darwin.go @@ -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 +} diff --git a/mmap/mmap_linux.go b/mmap/mmap_linux.go new file mode 100644 index 0000000..c99535f --- /dev/null +++ b/mmap/mmap_linux.go @@ -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 +} diff --git a/mmap/mmap_other.go b/mmap/mmap_other.go new file mode 100644 index 0000000..8c4ef04 --- /dev/null +++ b/mmap/mmap_other.go @@ -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 +} diff --git a/mmap/mmap_test.go b/mmap/mmap_test.go new file mode 100644 index 0000000..797fc5f --- /dev/null +++ b/mmap/mmap_test.go @@ -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)) + } +} diff --git a/mmap/mmap_windows.go b/mmap/mmap_windows.go new file mode 100644 index 0000000..e0d3414 --- /dev/null +++ b/mmap/mmap_windows.go @@ -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 +} diff --git a/sg_tools.go b/sg_tools.go new file mode 100644 index 0000000..68bd278 --- /dev/null +++ b/sg_tools.go @@ -0,0 +1 @@ +package tapewriter diff --git a/test.sh b/test.sh new file mode 100644 index 0000000..8cc3f07 --- /dev/null +++ b/test.sh @@ -0,0 +1,2 @@ +GOOS=linux go build -o ./output/ordercp ./cmd/ordercp +scp ./output/ordercp tape:ordercp diff --git a/tools.go b/tools.go new file mode 100644 index 0000000..5345a5c --- /dev/null +++ b/tools.go @@ -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 +} diff --git a/writer.go b/writer.go new file mode 100644 index 0000000..0dfb075 --- /dev/null +++ b/writer.go @@ -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 +}