mirror of
https://github.com/samuelncui/yatm.git
synced 2026-01-05 04:55:23 +00:00
feat: init
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -13,3 +13,4 @@
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
output/
|
||||
|
||||
78
block.go
Normal file
78
block.go
Normal 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
324
cmd/ordercp/main.go
Normal 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
47
cmd/writer/main.go
Normal 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
184
consts.go
Normal 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
2
encrypttape
Normal 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
19
go.mod
Normal 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
36
go.sum
Normal 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
16
maketape
Executable 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
|
||||
56
mmap/manual_test_program.go
Normal file
56
mmap/manual_test_program.go
Normal 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
136
mmap/mmap_darwin.go
Normal 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
140
mmap/mmap_linux.go
Normal 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
86
mmap/mmap_other.go
Normal 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
34
mmap/mmap_test.go
Normal 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
141
mmap/mmap_windows.go
Normal 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
1
sg_tools.go
Normal file
@@ -0,0 +1 @@
|
||||
package tapewriter
|
||||
2
test.sh
Normal file
2
test.sh
Normal file
@@ -0,0 +1,2 @@
|
||||
GOOS=linux go build -o ./output/ordercp ./cmd/ordercp
|
||||
scp ./output/ordercp tape:ordercp
|
||||
34
tools.go
Normal file
34
tools.go
Normal 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
243
writer.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user