| // 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 | 
 | } |