blob: df89fec57f9d2ce992ecf449d12e9d0cc068bc25 [file] [log] [blame]
// Copyright 2020 The Monogon Project Authors.
//
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package erofs
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"math"
)
// uncompressedInodeWriter exposes a io.Write-style interface for a single uncompressed inode. It splits the Write-calls
// into blocks and writes both the blocks and inode metadata. It is required to call Close() to ensure everything is
// properly written down before writing another inode.
type uncompressedInodeWriter struct {
buf bytes.Buffer
writer *Writer
inode inodeCompact
baseBlock uint32 // baseBlock == 0 implies this inode didn't allocate a block (yet).
writtenBytes int
legacyInodeNumber uint32
pathname string
}
func (i *uncompressedInodeWriter) allocateBlock() error {
bb, err := i.writer.allocateBlocks(1)
if err != nil {
return err
}
if i.baseBlock == 0 {
i.baseBlock = bb
}
return nil
}
func (i *uncompressedInodeWriter) flush(n int) error {
if err := i.allocateBlock(); err != nil {
return err
}
slice := i.buf.Next(n)
if _, err := i.writer.w.Write(slice); err != nil {
return err
}
// Always pad to BlockSize.
_, err := i.writer.w.Write(make([]byte, BlockSize-len(slice)))
return err
}
func (i *uncompressedInodeWriter) Write(b []byte) (int, error) {
i.writtenBytes += len(b)
if _, err := i.buf.Write(b); err != nil {
return 0, err
}
for i.buf.Len() >= BlockSize {
if err := i.flush(BlockSize); err != nil {
return 0, err
}
}
return len(b), nil
}
func (i *uncompressedInodeWriter) Close() error {
if i.buf.Len() > BlockSize {
panic("programming error")
}
inodeSize := binary.Size(i.inode)
if i.buf.Len()+inodeSize > BlockSize {
// Can't fit last part of data inline, write it in its own block.
if err := i.flush(i.buf.Len()); err != nil {
return err
}
}
if i.buf.Len() == 0 {
i.inode.Format = inodeFlatPlain << 1
} else {
// Colocate last part of data with inode.
i.inode.Format = inodeFlatInline << 1
}
if i.writtenBytes > math.MaxUint32 {
return errors.New("inodes bigger than 2^32 need the extended inode format which is unsupported by this library")
}
i.inode.Size = uint32(i.writtenBytes)
if i.baseBlock != 0 {
i.inode.Union = i.baseBlock
}
i.inode.HardlinkCount = 1
i.inode.InodeNumCompat = i.legacyInodeNumber
basePos, err := i.writer.allocateMetadata(inodeSize+i.buf.Len(), 32)
if err != nil {
return fmt.Errorf("failed to allocate metadata: %w", err)
}
i.writer.pathInodeMeta[i.pathname] = &uncompressedInodeMeta{
nid: uint64(basePos) / 32,
ftype: unixModeToFT(i.inode.Mode),
blockStart: int64(i.baseBlock),
blockLength: (int64(i.writtenBytes) / BlockSize) * BlockSize,
inlineStart: basePos + 32,
inlineLength: int64(i.buf.Len()),
writer: i.writer,
}
if err := binary.Write(i.writer.w, binary.LittleEndian, &i.inode); err != nil {
return err
}
if i.inode.Format&(inodeFlatInline<<1) != 0 {
// Data colocated in inode, if any.
_, err := i.writer.w.Write(i.buf.Bytes())
return err
}
return nil
}