diff --git a/shock-server/controller/node/index/index.go b/shock-server/controller/node/index/index.go index 6f56c4d1..a586eb4e 100644 --- a/shock-server/controller/node/index/index.go +++ b/shock-server/controller/node/index/index.go @@ -313,7 +313,7 @@ func IndexTypedRequest(ctx context.Context) { } count, indexFormat, err = idxer.Create(n.IndexPath() + "/" + idxType + ".idx") if err != nil { - err_msg := "err@node_Index: (idxer.Create) id=" + nid + ":" + err.Error() + err_msg := "err@node_Index: (idxer.Create) id=" + nid + ": " + err.Error() logger.Error(err_msg) responder.RespondWithError(ctx, http.StatusBadRequest, err_msg) return diff --git a/shock-server/node/file/format/fasta/fasta.go b/shock-server/node/file/format/fasta/fasta.go index db7c2197..08b9b755 100644 --- a/shock-server/node/file/format/fasta/fasta.go +++ b/shock-server/node/file/format/fasta/fasta.go @@ -39,45 +39,50 @@ func (self *Reader) Read() (sequence *seq.Seq, err error) { if self.r == nil { self.r = bufio.NewReader(self.f) } - var label, body []byte + var prev, read, label, body []byte + var eof bool for { - read, err := self.r.ReadBytes('>') - if len(read) > 1 { - lines := bytes.Split(read, []byte{'\n'}) - if len(lines) > 1 { - label = lines[0] - body = bytes.Join(lines[1:len(lines)-1], []byte{}) + read, err = self.r.ReadBytes('>') + // non eof error + if err != nil { + if err == io.EOF { + eof = true + } else { + return + } + } + if len(prev) > 0 { + read = append(prev, read...) + } + // only have '>' + if len(read) == 1 { + if eof { + break + } else { + continue } - break - } else if err != nil { - return nil, io.EOF } + // found an embedded '>' + if !bytes.Contains(read, []byte{'\n'}) { + prev = read + continue + } + // process lines + read = bytes.TrimSpace(bytes.TrimRight(read, ">")) + lines := bytes.Split(read, []byte{'\n'}) + if len(lines) > 1 { + label = lines[0] + body = bytes.Join(lines[1:], []byte{}) + } + break } if len(label) > 0 && len(body) > 0 { sequence = seq.New(label, body, nil) } else { - return nil, errors.New("Invalid fasta entry") - } - return -} - -// Read a single sequence and return it or an error. -func (self *Reader) ReadRaw(p []byte) (n int, err error) { - if self.r == nil { - self.r = bufio.NewReader(self.f) + err = errors.New("Invalid fasta entry") } - p[n] = byte('>') - n = 1 - for { - read, er := self.r.ReadBytes('>') - if len(read) > 1 { - copy(p[n:n+len(read)-1], read[0:len(read)-1]) - n += len(read) - 1 - break - } else if er != nil { - err = er - break - } + if eof { + err = io.EOF } return } @@ -88,27 +93,40 @@ func (self *Reader) GetReadOffset() (n int, err error) { self.r = bufio.NewReader(self.f) } n = 0 + var read []byte + var eof bool for { - read, er := self.r.ReadBytes('>') - if len(read) > 1 { - if er == io.EOF { - n += len(read) - } else if read[len(read)-2] != '\n' { + read, err = self.r.ReadBytes('>') + // non eof error + if err != nil { + if err == io.EOF { + eof = true + } else { + return + } + } + // handle embedded '>' + if (len(read) > 1) && bytes.Contains(read, []byte{'\n'}) { + // check for sequence + lines := bytes.Split(bytes.TrimSpace(bytes.TrimRight(read, ">")), []byte{'\n'}) + seq := bytes.Join(lines[1:], []byte{}) + if len(seq) == 0 { + err = errors.New("Invalid fasta entry") + return + } + if eof { n += len(read) - continue + err = io.EOF } else { n += len(read) - 1 - } - if read[len(read)-1] == '>' { - if unread_err := self.r.UnreadByte(); unread_err != nil { - err = unread_err - } + err = self.r.UnreadByte() } break - } else if len(read) == 1 { - n += 1 - } else if er != nil { - err = er + } else { + n += len(read) + } + if eof { + err = io.EOF break } } diff --git a/shock-server/node/file/format/fastq/fastq.go b/shock-server/node/file/format/fastq/fastq.go index b1476013..d0cce999 100644 --- a/shock-server/node/file/format/fastq/fastq.go +++ b/shock-server/node/file/format/fastq/fastq.go @@ -17,7 +17,7 @@ import ( ) var ( - Regex = regexp.MustCompile(`^[\n\r]*@[\S\t ]+[\n\r]+[A-Za-z\-]+[\n\r]+\+[\S\t ]*[\n\r]+\S*[\n\r]+`) + Regex = regexp.MustCompile(`^[\n\r]*@\S+[\S\t ]+[\n\r]+[A-Za-z\-]+[\n\r]+\+[\S\t ]*[\n\r]+\S*[\n\r]+`) ) // Fastq sequence format reader type. @@ -44,97 +44,85 @@ func NewReaderName(name string) (r seq.ReadRewinder, err error) { } // Read a single sequence and return it or an error. -// TODO: Does not read interleaved fastq. func (self *Reader) Read() (sequence *seq.Seq, err error) { if self.r == nil { self.r = bufio.NewReader(self.f) } - var line, label, seqBody, qualBody []byte - sequence = &seq.Seq{} + var seqId, seqBody, qualId, qualBody []byte - inQual := false -READ: + // skip empty lines only at eof + empty := false for { - if line, err = self.r.ReadBytes('\n'); err == nil { - if len(line) > 0 && line[len(line)-1] == '\r' { - line = line[:len(line)-1] - } - line = bytes.TrimSpace(line) - if len(line) == 0 { - continue - } - switch { - case !inQual && line[0] == '@': - label = line[1:] - case !inQual && line[0] == '+': - if len(label) == 0 { - return nil, errors.New("No ID line parsed at +line in fastq format") - } - if len(line) > 1 && bytes.Compare(label, line[1:]) != 0 { - return nil, errors.New("Quality ID does not match sequence ID") - } - inQual = true - case !inQual: - line = bytes.Join(bytes.Fields(line), nil) - seqBody = append(seqBody, line...) - case inQual: - line = bytes.Join(bytes.Fields(line), nil) - qualBody = append(qualBody, line...) - if len(qualBody) >= len(seqBody) { - break READ - } - } - } else { - return + seqId, err = self.r.ReadBytes('\n') + if err != nil { + break } + if len(seqId) > 1 { + break + } + empty = true } - if len(seqBody) != len(qualBody) { - return nil, errors.New("Quality length does not match sequence length") + if empty { + err = errors.New("Invalid format: empty line(s) between records") + return + } else if (err == io.EOF) && (len(seqId) > 1) { + err = errors.New("Invalid format: truncated fastq record") + return + } else if err != nil { + return + } else if !bytes.HasPrefix(seqId, []byte{'@'}) { + err = errors.New("Invalid format: id line does not start with @") + return + } + seqId = bytes.TrimSpace(seqId[1:]) + if len(seqId) == 0 { + err = errors.New("Invalid format: missing sequence ID") + return } - sequence = seq.New(label, seqBody, qualBody) - - return -} -func (self *Reader) ReadRaw(p []byte) (n int, err error) { - if self.r == nil { - self.r = bufio.NewReader(self.f) + seqBody, err = self.r.ReadBytes('\n') + if err == io.EOF { + err = errors.New("Invalid format: truncated fastq record") + return + } else if err != nil { + return } - curr := 0 - id, err := self.r.ReadBytes('\n') - if err != nil { - return 0, err - } else if !bytes.HasPrefix(id, []byte{'@'}) { - return 0, errors.New("Invalid format: id line does not start with @") + seqBody = bytes.TrimSpace(seqBody) + if len(seqBody) == 0 { + err = errors.New("Invalid format: empty sequence") + return } - copy(p[curr:len(id)+curr], id) - curr += len(id) - seq, err := self.r.ReadBytes('\n') - if err != nil { - return 0, err + qualId, err = self.r.ReadBytes('\n') + if err == io.EOF { + err = errors.New("Invalid format: truncated fastq record") + return + } else if err != nil { + return + } else if !bytes.HasPrefix(qualId, []byte{'+'}) { + err = errors.New("Invalid format: plus line does not start with +") + return } - copy(p[curr:len(seq)+curr], seq) - curr += len(seq) - - plus, err := self.r.ReadBytes('\n') - if err != nil { - return 0, err - } else if !bytes.HasPrefix(plus, []byte{'+'}) { - return 0, errors.New("Invalid format: plus line does not start with +") + qualId = bytes.TrimSpace(qualId) + if (len(qualId) > 1) && (bytes.Compare(seqId, qualId[1:]) != 0) { + err = errors.New("Invalid format: quality ID does not match sequence ID") + return } - copy(p[curr:len(plus)+curr], plus) - curr += len(plus) - qual, err := self.r.ReadBytes('\n') - if err != nil { - return 0, err - } else if len(seq) != len(qual) { - return 0, errors.New("Invalid format: length of sequence and quality lines do not match") + qualBody, err = self.r.ReadBytes('\n') + if (err == io.EOF) && (len(seqBody) != len(qualBody)) { + err = errors.New("Invalid format: length of sequence and quality lines do not match") + return + } else if err != nil { + return + } else if len(seqBody) != len(qualBody)-1 { + err = errors.New("Invalid format: length of sequence and quality lines do not match") + return } - copy(p[curr:len(qual)+curr], qual) - n = curr + len(qual) + qualBody = bytes.TrimSpace(qualBody) + + sequence = seq.New(seqId, seqBody, qualBody) return } @@ -143,47 +131,81 @@ func (self *Reader) GetReadOffset() (n int, err error) { if self.r == nil { self.r = bufio.NewReader(self.f) } + var seqId, seqBody, qualId, qualBody []byte curr := 0 - id, err := self.r.ReadBytes('\n') - if err != nil { - return 0, err - } else if !bytes.HasPrefix(id, []byte{'@'}) { - return 0, errors.New("Invalid format: id line does not start with @") + + // skip empty lines only at eof + empty := false + for { + seqId, err = self.r.ReadBytes('\n') + if err != nil { + break + } + if len(seqId) > 1 { + break + } + empty = true } - curr += len(id) - seq, err := self.r.ReadBytes('\n') - if err != nil { - return 0, err + if empty { + err = errors.New("Invalid format: empty line(s) between records") + return + } else if (err == io.EOF) && (len(seqId) > 1) { + err = errors.New("Invalid format: truncated fastq record") + return + } else if err != nil { + return + } else if !bytes.HasPrefix(seqId, []byte{'@'}) { + err = errors.New("Invalid format: id line does not start with @") + return + } else if len(seqId) == 2 { + err = errors.New("Invalid format: missing sequence ID") + return } - curr += len(seq) + curr += len(seqId) - plus, err := self.r.ReadBytes('\n') - if err != nil { - return 0, err - } else if !bytes.HasPrefix(plus, []byte{'+'}) { - return 0, errors.New("Invalid format: plus line does not start with +") + seqBody, err = self.r.ReadBytes('\n') + if err == io.EOF { + err = errors.New("Invalid format: truncated fastq record") + return + } else if err != nil { + return + } else if len(seqBody) == 1 { + err = errors.New("Invalid format: empty sequence") + return } - curr += len(plus) + curr += len(seqBody) - qual, err := self.r.ReadBytes('\n') - if len(qual) > 1 { - if err == io.EOF { - if len(seq)-1 != len(qual) { - return 0, errors.New("Invalid format: length of sequence and quality lines do not match") - } - n = curr + len(qual) - return n, nil - } else if err != nil { - return 0, err - } else if len(seq) != len(qual) { - return 0, errors.New("Invalid format: length of sequence and quality lines do not match") - } else { - n = curr + len(qual) - return - } + qualId, err = self.r.ReadBytes('\n') + if err == io.EOF { + err = errors.New("Invalid format: truncated fastq record") + return + } else if err != nil { + return + } else if !bytes.HasPrefix(qualId, []byte{'+'}) { + err = errors.New("Invalid format: plus line does not start with +") + return + } + qualIdTrim := bytes.TrimSpace(qualId) + if (len(qualIdTrim) > 1) && (bytes.Compare(bytes.TrimSpace(seqId[1:]), qualIdTrim[1:]) != 0) { + err = errors.New("Invalid format: quality ID does not match sequence ID") + return + } + curr += len(qualId) + + qualBody, err = self.r.ReadBytes('\n') + if (err == io.EOF) && (len(seqBody)-1 != len(qualBody)) { + err = errors.New("Invalid format: length of sequence and quality lines do not match") + return + } else if err != nil { + return + } else if len(seqBody) != len(qualBody) { + err = errors.New("Invalid format: length of sequence and quality lines do not match") + return } - return 0, err + + n = curr + len(qualBody) + return } // seek sequences which add up to a size close to the configured chunk size (conf.CHUNK_SIZE, e.g. 1M) diff --git a/shock-server/node/file/format/line/line.go b/shock-server/node/file/format/line/line.go index 8ee9b633..68e2d13f 100644 --- a/shock-server/node/file/format/line/line.go +++ b/shock-server/node/file/format/line/line.go @@ -38,15 +38,8 @@ func (self *Reader) GetReadOffset() (n int, err error) { if self.r == nil { self.r = bufio.NewReader(self.f) } - p, er := self.r.ReadBytes('\n') - for { - if len(p) > 1 { - n = len(p) - break - } else if er != nil { - err = er - break - } - } + var p []byte + p, err = self.r.ReadBytes('\n') + n = len(p) return } diff --git a/shock-server/node/file/format/multi/multi.go b/shock-server/node/file/format/multi/multi.go index 1e812a6c..964ead23 100644 --- a/shock-server/node/file/format/multi/multi.go +++ b/shock-server/node/file/format/multi/multi.go @@ -71,16 +71,6 @@ func (r *Reader) Read() (*seq.Seq, error) { return r.r.Read() } -func (r *Reader) ReadRaw(p []byte) (n int, err error) { - if r.r == nil { - err := r.DetermineFormat() - if err != nil { - return 0, err - } - } - return r.r.ReadRaw(p) -} - func (r *Reader) GetReadOffset() (n int, err error) { if r.r == nil { err := r.DetermineFormat() diff --git a/shock-server/node/file/format/sam/sam.go b/shock-server/node/file/format/sam/sam.go index 4c95ed1d..fe28bbb2 100644 --- a/shock-server/node/file/format/sam/sam.go +++ b/shock-server/node/file/format/sam/sam.go @@ -78,25 +78,6 @@ func (self *Reader) Read() (sequence *seq.Seq, err error) { return } -// Read a single sequence and return it or an error. (used for making record index) -func (self *Reader) ReadRaw(p []byte) (n int, err error) { - for { - read, er := self.r.ReadBytes('\n') - n += len(read) - if len(read) > 1 { - if read[0] == '@' { - continue - } - copy(p[0:len(read)], read[0:len(read)]) - break - } else if er != nil { - err = er - break - } - } - return -} - // Read a single sequence and return read offset for indexing. func (self *Reader) GetReadOffset() (n int, err error) { for { diff --git a/shock-server/node/file/format/sam/sam_test.go b/shock-server/node/file/format/sam/sam_test.go deleted file mode 100644 index d5018e6f..00000000 --- a/shock-server/node/file/format/sam/sam_test.go +++ /dev/null @@ -1,179 +0,0 @@ -package sam - -import ( - "fmt" - "io" - "io/ioutil" - "os" - "testing" -) - -var ( - gopath = os.Getenv("GOPATH") - sample = gopath + "/src/github.com/MG-RAST/Shock/shock-server/testdata/sample1.sam" - Idx [][]int64 //list of {offset, length} pair -) - -func TestValid(t *testing.T) { - f, _ := ioutil.ReadFile(sample) - println("valid") - println(Regex.Match(f)) - println("invalid:") - for _, s := range invalid { - println(Regex.MatchString(s)) - } -} - -var invalid = []string{`>S1 rank=0000056 x=2202.0 y=484.0 length=288 -TGAATGTATTCCAGTAAACCGCCCGCGCAAGTAGGCTTCAAATGCCTGCACATTGTCCGTGCCGCGTTTTCAAAGTTTCTGTTCTTCGCCCGAAAGAATAGGAAGAATTGATTTGGCGACCTGTTCGGAAACAATATCTTCAAGCGTCAAAACGTCGCTGAATTTTCATCAAAGGTCTTCGCCCAACACGTCGAATTATCCTTGACGCTCAAAAGCTGCGCTGAAATGCGAATTCTATCACCGACGCGGCGGAGATTACCGTCAAGAATAAAATCAACGCCGAGTTCG`, - `@SEQ_ID -GATTTGGGGTTCAAAGCAGTATCGATCAAATAGTAAATCCATTTGTTCAACTCACAGTTT -+ -!''*((((***+))%%%++)(%%%%).1***-+*''))**55CCF>>>>>>CCCCCCC65 -@SEQ_ID -GATTTGGGGTTCAAAGCAGTATCGATCAAATAGTAAATCCATTTGTTCAACTCACAGTTT -+ -!''*((((***+))%%%++)(%%%%).1***-+*''))**55CCF>>>>>>CCCCCCC65`, `@SEQ_ID -GATTTGGGGTTCAAAGCAGTATCGATCAAATAGTAAATCCATTTGTTCAACTCACAGTTT -+junkhhere -!''*((((***+))%%%++)(%%%%).1***-+*''))**55CCF>>>>>>CCCCCCC65 -@SEQ_ID -GATTTGGGGTTCAAAGCAGTATCGATCAAATAGTAAATCCATTTGTTCAACTCACAGTTT -+junkhhere -!''*((((***+))%%%++)(%%%%).1***-+*''))**55CCF>>>>>>CCCCCCC65`} - -func TestRead(t *testing.T) { - var ( - obtainN [][]byte - obtainS [][]byte - ) - - if r, err := NewReaderName(sample); err != nil { - t.Errorf("Failed to open test file %s: %v", sample, err.Error()) - } else { - for i := 0; i < 2; i++ { - var linect int - for { - if s, err := r.Read(); err != nil { - if err == io.EOF { - break - } else { - t.Errorf("Failed to read %s: %v", sample, err.Error()) - } - } else { - fmt.Println(i + 1) - obtainN = append(obtainN, s.ID) - obtainS = append(obtainS, s.Seq) - linect += 1 - //fmt.Printf("line %d = %s\n", linect, s.Seq) - } - } - obtainN = nil - obtainS = nil - if err = r.Rewind(); err != nil { - t.Errorf("Failed to rewind %s: %v", sample, err.Error()) - } - } - //r.Close() - } -} - -func TestReadRaw(t *testing.T) { - if r, err := NewReaderName(sample); err != nil { - t.Errorf("Failed to open test file %s: %v", sample, err.Error()) - } else { - for i := 0; i < 2; i++ { - var linect int - for { - buf := make([]byte, 32*1024) - - if n, err := r.ReadRaw(buf); err != nil { - if err == io.EOF { - break - } else { - t.Errorf("Fail to read in TestReadRaw() %s: %v", sample, err.Error()) - } - } else { - linect += 1 - fmt.Printf("line=%d, length=%d, line_content=%s\n", linect, n, buf) - } - } - - if err = r.Rewind(); err != nil { - t.Errorf("Failed to rewind %s: %v", sample, err.Error()) - } - } - //r.Close() - } -} - -func TestCreateIndex(t *testing.T) { - curr := int64(0) - - if r, err := NewReaderName(sample); err != nil { - t.Errorf("Failed to open test file %s: %v", sample, err.Error()) - } else { - for { - buf := make([]byte, 32*1024) - - if n, err := r.ReadRaw(buf); err != nil { - if err == io.EOF { - break - } else { - t.Errorf("Fail to read in TestCreatIndex() %s: %v", sample, err.Error()) - } - } else { - Idx = append(Idx, []int64{curr, int64(n)}) - curr += int64(n) - } - } - - if err = r.Rewind(); err != nil { - t.Errorf("Failed to rewind %s: %v", sample, err.Error()) - } - - fmt.Printf("indices= %v", Idx) - //r.Close() - } - return -} - -func TestReadSeqByIndex(t *testing.T) { - rs := make([]*io.SectionReader, 1000) - - if fd, err := os.Open(sample); err != nil { - t.Errorf("Failed to open test file %s: %v", sample, err.Error()) - } else { - for i := 1; i <= len(Idx); i++ { - pos := Idx[i-1][0] - length := Idx[i-1][1] - fmt.Printf("record %d: reading from pos=%d for length %d\n", i, pos, length) - if err != nil { - t.Errorf("invalid index part %d: %v", i, err.Error()) - return - } - rs = append(rs, io.NewSectionReader(fd, pos, length)) - } - - i := 1 - for _, sec_reader := range rs { - if sec_reader != nil { - buf := make([]byte, 32*1024) - if n, err := sec_reader.ReadAt(buf, 0); err != nil { - if err == io.EOF { - break - } else { - t.Errorf("Fail to read in TestReadSeqByIndex() %s: %v", sample, err.Error()) - } - } else { - fmt.Printf("record=%d, size=%d, seq=%s\n", i, n, buf) - i += 1 - } - - } - } - - fd.Close() - } - return -} diff --git a/shock-server/node/file/format/seq/seq.go b/shock-server/node/file/format/seq/seq.go index 9a29857e..3331b598 100644 --- a/shock-server/node/file/format/seq/seq.go +++ b/shock-server/node/file/format/seq/seq.go @@ -26,14 +26,12 @@ type ReadFormater interface { type Reader interface { Read() (*Seq, error) - ReadRaw(p []byte) (int, error) GetReadOffset() (int, error) SeekChunk(int64) (int64, error) } type ReadRewinder interface { Read() (*Seq, error) - ReadRaw(p []byte) (int, error) GetReadOffset() (int, error) SeekChunk(int64) (int64, error) Rewind() error diff --git a/shock-server/node/file/index/column.go b/shock-server/node/file/index/column.go index e3a70060..d2ace977 100644 --- a/shock-server/node/file/index/column.go +++ b/shock-server/node/file/index/column.go @@ -40,6 +40,7 @@ func CreateColumnIndex(c *column, column int, ofile string) (count int64, format defer f.Close() format = "array" + eof := false // identifies EOF curr := int64(0) // stores the offset position of the current index count = 0 // stores the number of indexed positions and get returned total_n := 0 // stores the number of bytes read for the current index record @@ -57,13 +58,17 @@ func CreateColumnIndex(c *column, column int, ofile string) (count int64, format err = er return } - break + eof = true } // skip empty line if n <= 1 { total_n += n line_count += 1 - continue + if eof { + break + } else { + continue + } } // split line by columns and test if column value has changed slices := bytes.Split(buf, []byte("\t")) @@ -96,6 +101,10 @@ func CreateColumnIndex(c *column, column int, ofile string) (count int64, format } total_n += n line_count += 1 + + if eof { + break + } } // Calculating position in byte array diff --git a/shock-server/node/file/index/line.go b/shock-server/node/file/index/line.go index ef335ada..77a7fa01 100644 --- a/shock-server/node/file/index/line.go +++ b/shock-server/node/file/index/line.go @@ -40,6 +40,7 @@ func (l *lineRecord) Create(file string) (count int64, format string, err error) defer f.Close() format = "array" + eof := false curr := int64(0) count = 0 buffer_pos := 0 // used to track the location in our byte array @@ -54,8 +55,9 @@ func (l *lineRecord) Create(file string) (count int64, format string, err error) err = er return } - break + eof = true } + x := (buffer_pos * 16) if x == 16777216 { f.Write(b[:]) @@ -70,6 +72,10 @@ func (l *lineRecord) Create(file string) (count int64, format string, err error) curr += int64(n) count += 1 buffer_pos += 1 + + if eof { + break + } } if buffer_pos != 0 { f.Write(b[:buffer_pos*16]) diff --git a/shock-server/node/file/index/record.go b/shock-server/node/file/index/record.go index 33135e45..c8f3b86f 100644 --- a/shock-server/node/file/index/record.go +++ b/shock-server/node/file/index/record.go @@ -41,6 +41,7 @@ func (i *record) Create(file string) (count int64, format string, err error) { defer f.Close() format = "array" + eof := false curr := int64(0) count = 0 buffer_pos := 0 // used to track the location in our byte array @@ -48,18 +49,19 @@ func (i *record) Create(file string) (count int64, format string, err error) { // Writing index file in 16MB chunks var b [16777216]byte for { - // io.EOF error does not get returned from GetReadOffset() until all sequences - // have been read. Thus, io.EOF for last fasta read is not returned as it is by bufio.ReadBytes(). - // This was primarily implemented as such for agreement in the behavior between the fasta and - // the fastq readers. n, er := i.r.GetReadOffset() if er != nil { if er != io.EOF { err = er return } + eof = true + } + + if eof && (n == 0) { break } + x := (buffer_pos * 16) if x == 16777216 { f.Write(b[:]) @@ -74,6 +76,10 @@ func (i *record) Create(file string) (count int64, format string, err error) { curr += int64(n) count += 1 buffer_pos += 1 + + if eof { + break + } } if buffer_pos != 0 { f.Write(b[:buffer_pos*16])