Repository: Azure/azure-storage-fuse

Issue:
BlockCache::OpenFile : Failed to get block list (BlobFuse2)
**Which version of blobfuse was used?**
blobfuse2 --version
blobfuse2 version 2.5.0

rpm -qa | grep -E 'fuse'
blobfuse2-2.5.0-1.x86_64
fuse3-libs-3.6.1-4.el7.x86_64
blobfuse-1.4.2-1.el7_9.x86_64
fuse-2.9.2-11.el7.x86_64
fuse-libs-2.9.2-11.el7.x86_64
fuse3-3.6.1-4.el7.x86_64


**Which OS distribution and version are you using?**

Spident
Red Hat Enterprise Linux Server release 7.9 (Maipo) x86_64

uname -a
Linux azr-eus2l1530 3.10.0-1160.136.1.el7.x86_64 #1 SMP Tue Jul 8 07:32:00 EDT 2025 x86_64 x86_64 x86_64 GNU/Linux


**If relevant, please share your mount command.**

/usr/bin/blobfuse2 mount /oracle/Blob_BAK --config-file=Blobfuse2-BlockCacheConfig2.yaml

cat Blobfuse2-BlockCacheConfig2.yaml
allow-other: true
logging:
  type: base
  level: log_debug
  file-path: /var/log/blobfuse2/blobfuse2_blockcache.log
  max-file-size-mb: 64
  file-count: 2
  track-time: true
libfuse:
  fuse-trace: true
  default-permission: 0777
  attribute-expiration-sec: 240
  entry-expiration-sec: 240
  negative-entry-expiration-sec: 120
  ignore-open-flags: true
  read-only: true
block_cache:
  block-size-mb: 64
  mem-size-mb: 10240
  prefetch: 12                      # Number of blocks to prefetch for sequential reads (e.g., RMAN read-backups)
  parallelism: 128                  # Number of parallel upload/download threads operating with disk cache
  path: /mnt/resource/blobfuse2tmp_block
  disk-size-mb: 81920
  disk-timeout-sec: 60
azstorage:
  type: block
  account-name: xxxx
  account-key: xxxx
  endpoint: xxxxx
  container: xxxxx
  max-concurrency: 24
  update-md5: false
  validate-md5: false
  virtual-directory: true
  max-results-for-list: 2
  disable-compression: false
  telemetry: ""
  honour-acl: false
  cpk-enabled: false
  cpk-encryption-key: ""
  cpk-encryption-key-sha256: ""
components:
  - libfuse
  - block_cache
  - attr_cache
  - azstorage

**What was the issue encountered?**

This is the same issue reported previously in https://github.com/Azure/azure-storage-fuse/issues/1536

Wed Aug 27 09:17:51 EDT 2025 : blobfuse2[19282] : [/oracle/Blob_BAK] LOG_DEBUG [utils.go (167)]: SDK(Retry) : response 200
Wed Aug 27 09:17:51 EDT 2025 : blobfuse2[19282] : [/oracle/Blob_BAK] LOG_DEBUG [utils.go (167)]: SDK(Retry) : exit due to non-retriable status code
Wed Aug 27 09:17:51 EDT 2025 : blobfuse2[19282] : [/oracle/Blob_BAK] LOG_ERR [block_cache.go (421)]: BlockCache::OpenFile : Failed to get block list of ASD_270536290/ASD_270536290_20250827_94388_1 [<nil>]
Wed Aug 27 09:17:51 EDT 2025 : blobfuse2[19282] : [/oracle/Blob_BAK] LOG_ERR [libfuse_handler.go (712)]: Libfuse::libfuse_open : Failed to open ASD_270536290/ASD_270536290_20250827_94388_1 [failed to retrieve block list for ASD_270536290/ASD_270536290_20250827_94388_1]

But the file was created as per the image below (and attached)

<img width="1459" height="174" alt="Image" src="https://github.com/user-attachments/assets/ed312a82-703c-46e6-abeb-daff402c7118" />

**Have you found a mitigation/solution?**
No

**Please share logs if available.**
Attached



Discussion:
@AdrianAllan0: [blobfuse2_blockcache.log](https://github.com/user-attachments/files/22260142/blobfuse2_blockcache.log)@vibhansa-msft: How was this file created ? As per logs when blobfuse tried to retrieve the block-list for this particular blob, from backend we received an empty list. @AdrianAllan0: Hi Vikas, thanks for your reply.
The file was created by the RMAN archive log backup, I am attaching the output of this execution attached here.

Thanks!!!

[rmanBackup_output.txt](https://github.com/user-attachments/files/22274971/rmanBackup_output.txt)@syeleti-msft: Hi @AdrianAllan0 , This is an active bug which we're currently working on, will get resolved in our next release. Thanks@AdrianAllan0: Hi @syeleti-msft, thans for the reply, is there an ETA for this release? Also, is there any possible workaround?@syeleti-msft: next release is scheduled at the month end. you can try use file_cache.@AdrianAllan0: Hello @syeleti-msft , can you please confirm if this bug is now fixed in the last release?@syeleti-msft: Hi @AdrianAllan0 , It will be merged soon and we are planning to release late this month.

Solution patch:
diff --git a/.golangci.yml b/.golangci.yml
index e5dd6f9..a5e32a1 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -54,4 +54,4 @@ formatters:
     paths:
       - third_party$
       - builtin$
-      - examples$
\ No newline at end of file
+      - examples$
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a12e88b..692f415 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,10 @@
 - Fail file open operation if the file being downloaded by file-cache can not fit in available disk space (either configured by user or computed implicitly by blobfuse). User application will receive ENOSPC (no space left on device) in response to file open call.
 - Mount will fail if FNS account is mounted as HNS account.
 - Redirect Stack trace to log file (WORK_DIR/mount_path.pid.trace) instead of console in case of panic for better debuggability.
+- Truncating the file in file_cache resulting in OOM panic by go-runtime in some scenarios.
+- Open file error(No BlockList error) in block_cache when file is truncated before to less than 256MiB.
+- Prevent reusing the same block ID in truncate operation which could lead to issues.
+
 
 ## 2.5.0 (2025-07-17)
 **Bug Fixes**
diff --git a/azure-pipeline-templates/verbose-tests.yml b/azure-pipeline-templates/verbose-tests.yml
index 700bad3..cbc31b1 100644
--- a/azure-pipeline-templates/verbose-tests.yml
+++ b/azure-pipeline-templates/verbose-tests.yml
@@ -221,6 +221,20 @@ steps:
       
 #--------------------------------------- Tests: End to end tests with different File Cache configurations ------------------------------------------
 
+  - template: e2e-tests-spcl.yml
+    parameters:
+      conf_template: azure_key_directio.yaml
+      config_file: ${{ parameters.config }}
+      adls: ${{ parameters.adls }}
+      account_name: ${{ parameters.account_name }}
+      account_key: ${{ parameters.account_key }}
+      account_type: ${{ parameters.account_type }}
+      idstring: "${{ parameters.service }} with File-cache"
+      distro_name: ${{ parameters.distro_name }}
+      quick_test: false
+      verbose_log: ${{ parameters.verbose_log }}
+      clone: false
+
   - template: e2e-tests-spcl.yml
     parameters:
       conf_template: azure_key_lru_purge.yaml
diff --git a/common/types.go b/common/types.go
index b01b323..641d639 100644
--- a/common/types.go
+++ b/common/types.go
@@ -154,12 +154,11 @@ type LogConfig struct {
 	Tag         string // logging tag which can be either blobfuse2 or bfusemon
 }
 
-// Flags for blocks
+// Flags for block
 const (
 	BlockFlagUnknown uint16 = iota
 	DirtyBlock
 	TruncatedBlock
-	RemovedBlocks
 )
 
 type Block struct {
@@ -181,14 +180,11 @@ func (block *Block) Truncated() bool {
 	return block.Flags.IsSet(TruncatedBlock)
 }
 
-func (block *Block) Removed() bool {
-	return block.Flags.IsSet(RemovedBlocks)
-}
-
 // Flags for block offset list
 const (
-	BolFlagUnknown uint16 = iota
-	SmallFile
+	BlobFlagUnknown     uint16 = iota
+	BlobFlagHasNoBlocks        // set if the blob does not have any blocks
+	BlobFlagBlockListModified
 )
 
 // list that holds blocks containing ids and corresponding offsets
@@ -200,13 +196,39 @@ type BlockOffsetList struct {
 	Mtime         time.Time
 }
 
-// Dirty : Handle is dirty or not
-func (bol *BlockOffsetList) SmallFile() bool {
-	return bol.Flags.IsSet(SmallFile)
+func (bol *BlockOffsetList) HasNoBlocks() bool {
+	return len(bol.BlockList) == 0
+}
+
+func (bol *BlockOffsetList) IsBlockListModified() bool {
+	return bol.Flags.IsSet(BlobFlagBlockListModified)
+}
+
+func (bol *BlockOffsetList) ValidateBlockListAgainstFileSize(fileSize int64) bool {
+	if bol.HasNoBlocks() {
+		return fileSize == 0
+	}
+	if bol.BlockList[len(bol.BlockList)-1].EndIndex != fileSize {
+		return false
+	}
+	return true
+}
+
+func (bol *BlockOffsetList) HasAllBlocksWithSameBlockSize() (blockSize int64, ok bool) {
+	if bol.HasNoBlocks() {
+		return 0, true
+	}
+	blockSize = bol.BlockList[0].EndIndex - bol.BlockList[0].StartIndex
+	for _, blk := range bol.BlockList {
+		if blk.EndIndex-blk.StartIndex != blockSize {
+			return 0, false
+		}
+	}
+	return blockSize, true
 }
 
 // return true if item found and index of the item
-func (bol BlockOffsetList) BinarySearch(offset int64) (bool, int) {
+func (bol *BlockOffsetList) BinarySearch(offset int64) (bool, int) {
 	lowerBound := 0
 	size := len(bol.BlockList)
 	higherBound := size - 1
diff --git a/component/attr_cache/attr_cache.go b/component/attr_cache/attr_cache.go
index c57bc0e..b5023f2 100644
--- a/component/attr_cache/attr_cache.go
+++ b/component/attr_cache/attr_cache.go
@@ -494,7 +494,7 @@ func (ac *AttrCache) TruncateFile(options internal.TruncateFileOptions) error {
 		// no need to truncate the name of the file
 		value, found := ac.cacheMap[options.Name]
 		if found && value.valid() && value.exists() {
-			value.setSize(options.Size)
+			value.setSize(options.NewSize)
 		}
 		// todo: invalidating path here rather than updating with etag
 		// due to some changes that are required in az storage comp which
diff --git a/component/azstorage/azstorage.go b/component/azstorage/azstorage.go
index 811060e..500f5d9 100644
--- a/component/azstorage/azstorage.go
+++ b/component/azstorage/azstorage.go
@@ -498,11 +498,11 @@ func (az *AzStorage) GetFileBlockOffsets(options internal.GetFileBlockOffsetsOpt
 }
 
 func (az *AzStorage) TruncateFile(options internal.TruncateFileOptions) error {
-	log.Trace("AzStorage::TruncateFile : %s to %d bytes", options.Name, options.Size)
-	err := az.storage.TruncateFile(options.Name, options.Size)
+	log.Trace("AzStorage::TruncateFile : %s to %d bytes", options.Name, options.NewSize)
+	err := az.storage.TruncateFile(options)
 
 	if err == nil {
-		azStatsCollector.PushEvents(truncateFile, options.Name, map[string]any{size: options.Size})
+		azStatsCollector.PushEvents(truncateFile, options.Name, map[string]any{size: options.NewSize})
 		azStatsCollector.UpdateStats(stats_manager.Increment, truncateFile, (int64)(1))
 	}
 	return err
diff --git a/component/azstorage/block_blob.go b/component/azstorage/block_blob.go
index fba882a..20d158f 100644
--- a/component/azstorage/block_blob.go
+++ b/component/azstorage/block_blob.go
@@ -43,6 +43,7 @@ import (
 	"os"
 	"path/filepath"
 	"reflect"
+	"sort"
 	"strings"
 	"syscall"
 	"time"
@@ -949,6 +950,7 @@ func (bb *BlockBlob) ReadBuffer(name string, offset int64, length int64) ([]byte
 }
 
 // ReadInBuffer : Download specific range from a file to a user provided buffer
+// Specifying "0" len will download the entire blob.
 func (bb *BlockBlob) ReadInBuffer(name string, offset int64, length int64, data []byte, etag *string) error {
 	// log.Trace("BlockBlob::ReadInBuffer : name %s", name)
 	if etag != nil {
@@ -1183,10 +1185,12 @@ func (bb *BlockBlob) GetFileBlockOffsets(name string) (*common.BlockOffsetList,
 
 	// if block list empty its a small file
 	if len(storageBlockList.CommittedBlocks) == 0 {
-		blockList.Flags.Set(common.SmallFile)
+		blockList.BlockIdLength = common.BlockIDLength
 		return &blockList, nil
 	}
 
+	blockList.BlockList = make([]*common.Block, 0, len(storageBlockList.CommittedBlocks))
+
 	for _, block := range storageBlockList.CommittedBlocks {
 		blk := &common.Block{
 			Id:         *block.Name,
@@ -1214,6 +1218,19 @@ func (bb *BlockBlob) createBlock(blockIdLength, startIndex, size int64) *common.
 	return newBlock
 }
 
+func (bb *BlockBlob) createBlockFromBuffer(blockIdLength, startIndex int64, data []byte) *common.Block {
+	newBlockId := common.GetBlockID(blockIdLength)
+	newBlock := &common.Block{
+		Id:         newBlockId,
+		StartIndex: startIndex,
+		EndIndex:   startIndex + int64(len(data)),
+		Data:       data,
+	}
+	// mark dirty since it is a new block with data
+	newBlock.Flags.Set(common.DirtyBlock)
+	return newBlock
+}
+
 // create new blocks based on the offset and total length we're adding to the file
 func (bb *BlockBlob) createNewBlocks(blockList *common.BlockOffsetList, offset, length int64) (int64, error) {
 	blockSize := bb.Config.blockSize
@@ -1243,154 +1260,282 @@ func (bb *BlockBlob) createNewBlocks(blockList *common.BlockOffsetList, offset,
 	return bufferSize, nil
 }
 
-func (bb *BlockBlob) removeBlocks(blockList *common.BlockOffsetList, size int64, name string) *common.BlockOffsetList {
-	_, index := blockList.BinarySearch(size)
-	// if the start index is equal to new size - block should be removed - move one index back
-	if blockList.BlockList[index].StartIndex == size {
-		index = index - 1
+// This function is called when the file is expanded using truncate operation and the new size is greater than the old
+// size of the file.
+func (bb *BlockBlob) createNewBlocksTruncate(blockList *common.BlockOffsetList, options *internal.TruncateFileOptions) error {
+	log.Trace("BlockBlob::createNewBlocksTruncate : name: %s, old size: %d, new size: %d", options.Name, options.OldSize,
+		options.NewSize)
+
+	numOfBlocks := int64(len(blockList.BlockList))
+	length := options.NewSize - options.OldSize
+
+	blockSize := options.BlockSize
+	if blockSize == 0 {
+		blockSize = bb.Config.blockSize
+		if blockSize == 0 {
+			// TODO: This should be set in init of blobfuse
+			blockSize = 16 * 1024 * 1024
+		}
+
+		blocksNeeded := (length + blockSize - 1) / blockSize
+
+		if numOfBlocks+blocksNeeded > blockblob.MaxBlocks {
+			// if we cannot accommodate the data with current block size then recalculate the block size
+			availableBlocks := blockblob.MaxBlocks - numOfBlocks
+			blockSize = (length + availableBlocks - 1) / availableBlocks
+			if blockSize > blockblob.MaxStageBlockBytes {
+				return errors.New("cannot accommodate data within the block limit")
+			}
+		}
+	} else {
+		// Case when user has specified the block size from previous component
+		// Say if a 10MB file is created using block_cache with blockSize 8MB, say it's block list would be 1(8M), 2(2M).
+		// Now say user is trying to truncate it to say 20MB. Then we should first download the second block and modify
+		// it (i.e., New block list would be 1(8M), 2(8M), 3(4M)).
+
+		// Calculate how many blocks we need with this block size
+		blocksNeeded := (length + blockSize - 1) / blockSize
+		// For user specified block size, validate if we can accommodate the data within the block limit
+		if numOfBlocks+blocksNeeded > blockblob.MaxBlocks {
+			return fmt.Errorf("cannot accommodate data within the block limit with configured block-size: %d", blockSize)
+		}
+
+		// if last block is not aligned to block size then adjust it
+		if len(blockList.BlockList) > 0 {
+			lastBlock := blockList.BlockList[len(blockList.BlockList)-1]
+
+			if lastBlock.EndIndex-lastBlock.StartIndex < blockSize {
+				// last block is smaller than block size, so adjust it
+				lastBlock.EndIndex = min(lastBlock.StartIndex+blockSize, options.NewSize)
+
+				lastBlock.Data = make([]byte, lastBlock.EndIndex-lastBlock.StartIndex)
+				lastBlock.Flags.Set(common.DirtyBlock)
+
+				// create the new block id for this block otherwise it would corrupt the state of the blob.
+				lastBlock.Id = common.GetBlockID(blockList.BlockIdLength)
+
+				err := bb.ReadInBuffer(options.Name, lastBlock.StartIndex, lastBlock.EndIndex-lastBlock.StartIndex, lastBlock.Data, nil)
+				if err != nil {
+					log.Err("BlockBlob::createNewBlocksTruncate : Failed to adjust last block %s [%v]", options.Name, err)
+					return err
+				}
+			}
+
+		}
 	}
+
+	var startIdx int64 = 0
+	if len(blockList.BlockList) > 0 {
+		startIdx = blockList.BlockList[len(blockList.BlockList)-1].EndIndex
+	}
+
+	for i := startIdx; i < options.NewSize; i += blockSize {
+		curBlkSize := min(blockSize, options.NewSize-i)
+
+		newBlock := bb.createBlock(blockList.BlockIdLength, i, curBlkSize)
+		blockList.BlockList = append(blockList.BlockList, newBlock)
+	}
+
+	blockList.Flags.Set(common.BlobFlagBlockListModified)
+
+	return nil
+}
+
+func (bb *BlockBlob) removeBlocksTruncate(blockList *common.BlockOffsetList, options *internal.TruncateFileOptions) error {
+	log.Trace("BlockBlob::removeBlocksTruncate : name: %s, old size: %d, new size: %d", options.Name, options.OldSize,
+		options.NewSize)
+
+	if len(blockList.BlockList) == 0 {
+		return errors.New("removeBlocksTruncate: block list is empty, cannot remove blocks")
+	}
+
+	size := options.NewSize
+
+	idx := sort.Search(len(blockList.BlockList), func(i int) bool {
+		return blockList.BlockList[i].StartIndex >= size
+	})
+
+	idx-- // move one index back to get the last block which is less than or equal to new size
+
+	log.Debug("BlockBlob::removeBlocksTruncate : idx: %d, blockList length: %d, idx.start: %d, idx.end: %d, new size: %d",
+		idx, len(blockList.BlockList), blockList.BlockList[idx].StartIndex, blockList.BlockList[idx].EndIndex, size)
+
 	// if the file we're shrinking is in the middle of a block then shrink that block
-	if blockList.BlockList[index].EndIndex > size {
-		blk := blockList.BlockList[index]
+	if blockList.BlockList[idx].EndIndex > size {
+		blk := blockList.BlockList[idx]
 		blk.EndIndex = size
 		blk.Data = make([]byte, blk.EndIndex-blk.StartIndex)
 		blk.Flags.Set(common.DirtyBlock)
 
-		err := bb.ReadInBuffer(name, blk.StartIndex, blk.EndIndex-blk.StartIndex, blk.Data, nil)
+		// create the new block id for this block otherwise it would corrupt the state of the blob.
+		blk.Id = common.GetBlockID(blockList.BlockIdLength)
+
+		err := bb.ReadInBuffer(options.Name, blk.StartIndex, blk.EndIndex-blk.StartIndex, blk.Data, nil)
 		if err != nil {
-			log.Err("BlockBlob::removeBlocks : Failed to remove blocks %s [%s]", name, err.Error())
+			log.Err("BlockBlob::removeBlocksTruncate : Failed to remove blocks %s [%v]", options.Name, err)
+			return err
 		}
-
 	}
-	blk := blockList.BlockList[index]
-	blk.Flags.Set(common.RemovedBlocks)
-	blockList.BlockList = blockList.BlockList[:index+1]
 
-	return blockList
+	blockList.BlockList = blockList.BlockList[:idx+1]
+	blockList.Flags.Set(common.BlobFlagBlockListModified)
+
+	return nil
 }
 
-func (bb *BlockBlob) TruncateFile(name string, size int64) error {
-	// log.Trace("BlockBlob::TruncateFile : name=%s, size=%d", name, size)
-	attr, err := bb.GetAttr(name)
-	if err != nil {
-		log.Err("BlockBlob::TruncateFile : Failed to get attributes of file %s [%s]", name, err.Error())
-		if err == syscall.ENOENT {
+func (bb *BlockBlob) TruncateFile(options internal.TruncateFileOptions) error {
+	log.Trace("BlockBlob::TruncateFile : name: %s, old size: %d, new size: %d",
+		options.Name, options.OldSize, options.NewSize)
+
+	// If old size is not specified, get it from the storage.
+	if options.OldSize == -1 {
+		attr, err := bb.GetAttr(options.Name)
+		if err != nil {
+			log.Err("BlockBlob::TruncateFile : Failed to get attributes of file %s [%v]", options.Name, err)
 			return err
 		}
+
+		options.OldSize = attr.Size
+
+		log.Trace("BlockBlob::TruncateFile : name: %s, old size: %d[Got from storage], new size: %d",
+			options.Name, options.OldSize, options.NewSize)
 	}
-	if size == 0 || attr.Size == 0 {
-		// If we are resizing to a value > 1GB then we need to upload multiple blocks to resize
-		if size > 1*common.GbToBytes {
-			blkSize := int64(16 * common.MbToBytes)
-			blobName := filepath.Join(bb.Config.prefixPath, name)
-			blobClient := bb.Container.NewBlockBlobClient(blobName)
-
-			blkList := make([]string, 0)
-			id := common.GetBlockID(common.BlockIDLength)
-
-			for i := 0; size > 0; i++ {
-				if i == 0 || size < blkSize {
-					// Only first and last block we upload and rest all we replicate with the first block itself
-					if size < blkSize {
-						blkSize = size
-						id = common.GetBlockID(common.BlockIDLength)
-					}
-					data := make([]byte, blkSize)
-
-					_, err = blobClient.StageBlock(context.Background(),
-						id,
-						streaming.NopCloser(bytes.NewReader(data)),
-						&blockblob.StageBlockOptions{
-							CPKInfo: bb.blobCPKOpt,
-						})
-					if err != nil {
-						log.Err("BlockBlob::TruncateFile : Failed to stage block for %s [%s]", name, err.Error())
-						return err
-					}
-				}
-				blkList = append(blkList, id)
-				size -= blkSize
-			}
 
-			err = bb.CommitBlocks(blobName, blkList, nil)
-			if err != nil {
-				log.Err("BlockBlob::TruncateFile : Failed to commit blocks for %s [%s]", name, err.Error())
-				return err
-			}
-		} else {
-			err := bb.WriteFromBuffer(name, nil, make([]byte, size))
-			if err != nil {
-				log.Err("BlockBlob::TruncateFile : Failed to set the %s to 0 bytes [%s]", name, err.Error())
-			}
+	if options.OldSize == options.NewSize {
+		return nil
+	}
+
+	if options.NewSize == 0 {
+		var buf []byte
+		if err := bb.WriteFromBuffer(options.Name, nil, buf); err != nil {
+			log.Err("BlockBlob::TruncateFile : Failed to truncate file %s to zero size [%v]", options.Name, err)
+			return err
+		}
+		return nil
+	}
+
+	// Handle files whose new size <= 256MiB && when block size is not specified.
+	if options.NewSize <= blockblob.MaxUploadBlobBytes && options.BlockSize == 0 {
+		if err := bb.TruncateFileWithoutBlocks(&options); err != nil {
+			log.Err("BlockBlob::TruncateFile : Failed to truncate file %s[%v]", options.Name, err)
+			return err
 		}
+		return nil
+	}
+
+	// Truncate file by managing blocks
+	if err := bb.TruncateFileUsingBlocks(&options); err != nil {
+		log.Err("BlockBlob::TruncateFile : Failed to truncate file %s[%v]", options.Name, err)
 		return err
 	}
 
-	//If new size is less than 256MB
-	if size < blockblob.MaxUploadBlobBytes {
-		data, err := bb.HandleSmallFile(name, size, attr.Size)
+	return nil
+}
+
+func (bb *BlockBlob) TruncateFileWithoutBlocks(options *internal.TruncateFileOptions) error {
+	log.Trace("BlockBlob::TruncateFileWithoutBlocks : name: %s, old size: %d, new size: %d", options.Name,
+		options.OldSize, options.NewSize)
+	var err error
+
+	buf := make([]byte, options.NewSize)
+
+	if options.OldSize > 0 {
+		// Read the file
+		err = bb.ReadInBuffer(options.Name, 0, min(options.NewSize, options.OldSize), buf, nil)
 		if err != nil {
-			log.Err("BlockBlob::TruncateFile : Failed to read small file %s", name, err.Error())
+			log.Err("BlockBlob::TruncateFileWithoutBlocks : Failed to read small file %s[%v]", options.Name, err)
 			return err
 		}
-		err = bb.WriteFromBuffer(name, nil, data)
-		if err != nil {
-			log.Err("BlockBlob::TruncateFile : Failed to write from buffer file %s", name, err.Error())
+	}
+
+	// Write the file
+	err = bb.WriteFromBuffer(options.Name, nil, buf)
+	if err != nil {
+		log.Err("BlockBlob::TruncateFileWithoutBlocks : Failed to write from buffer file %s[%v]", options.Name, err)
+		return err
+	}
+
+	return nil
+}
+
+func (bb *BlockBlob) TruncateFileUsingBlocks(options *internal.TruncateFileOptions) error {
+	log.Trace("BlockBlob::TruncateFileUsingBlocks : name: %s, old size: %d, new size: %d", options.Name,
+		options.OldSize, options.NewSize)
+	var err error
+
+	blob, err := bb.GetFileBlockOffsets(options.Name)
+	if err != nil {
+		log.Err("BlockBlob::TruncateFileUsingBlocks : Failed to get block offsets for file %s[%v]", options.Name, err)
+		return err
+	}
+
+	// If blob has no blocks, we should convert the blob into blocks first.
+	if blob.HasNoBlocks() && options.OldSize > 0 {
+		if options.OldSize > blockblob.MaxUploadBlobBytes {
+			err = fmt.Errorf("Blob %v has size %d bytes greater than 256MB but has no blocks, inconsistent state",
+				options.Name, options.OldSize)
+			log.Err("BlockBlob::TruncateFileUsingBlocks : %v", err)
 			return err
 		}
-	} else {
-		bol, err := bb.GetFileBlockOffsets(name)
+
+		buf := make([]byte, options.OldSize)
+
+		// Read the file
+		err = bb.ReadInBuffer(options.Name, 0, options.OldSize, buf, nil)
 		if err != nil {
-			log.Err("BlockBlob::TruncateFile : Failed to get block list of file %s [%s]", name, err.Error())
+			log.Err("BlockBlob::TruncateFileUsingBlocks : Failed to read small file %s[%v]", options.Name, err)
 			return err
 		}
-		if bol.SmallFile() {
-			data, err := bb.HandleSmallFile(name, size, attr.Size)
-			if err != nil {
-				log.Err("BlockBlob::TruncateFile : Failed to read small file %s", name, err.Error())
-				return err
-			}
-			err = bb.WriteFromBuffer(name, nil, data)
-			if err != nil {
-				log.Err("BlockBlob::TruncateFile : Failed to write from buffer file %s", name, err.Error())
-				return err
-			}
-		} else {
-			if size < attr.Size {
-				bol = bb.removeBlocks(bol, size, name)
-			} else if size > attr.Size {
-				_, err = bb.createNewBlocks(bol, bol.BlockList[len(bol.BlockList)-1].EndIndex, size-attr.Size)
-				if err != nil {
-					log.Err("BlockBlob::TruncateFile : Failed to create new blocks for file %s", name, err.Error())
-					return err
-				}
+
+		// Create blocks for the blob.
+		blockSize := options.BlockSize
+		if blockSize == 0 {
+			blockSize = bb.Config.blockSize
+			if blockSize == 0 {
+				// TODO: This should be set in init of blobfuse
+				blockSize = 16 * 1024 * 1024
 			}
-			err = bb.StageAndCommit(name, bol)
-			if err != nil {
-				log.Err("BlockBlob::TruncateFile : Failed to stage and commit file %s", name, err.Error())
-				return err
+
+			for i := int64(0); i < options.OldSize; i += blockSize {
+				blkSize := min(blockSize, options.OldSize-i)
+				newBlock := bb.createBlockFromBuffer(blob.BlockIdLength, i, buf[i:i+blkSize])
+				blob.BlockList = append(blob.BlockList, newBlock)
 			}
 		}
+		blob.Flags.Set(common.BlobFlagBlockListModified)
+	} else {
+		// If blob has blocks, we should validate the block list against the old size.
+		if ok := blob.ValidateBlockListAgainstFileSize(options.OldSize); !ok {
+			err = fmt.Errorf("Blob %v has blocks that do not match the old size %d bytes, inconsistent state",
+				options.Name, options.OldSize)
+			log.Err("BlockBlob::TruncateFileUsingBlocks : %v", err)
+			return err
+		}
 	}
 
-	return nil
-}
-
-func (bb *BlockBlob) HandleSmallFile(name string, size int64, originalSize int64) ([]byte, error) {
-	var data = make([]byte, size)
-	var err error
-	if size > originalSize {
-		err = bb.ReadInBuffer(name, 0, 0, data, nil)
+	if options.NewSize < options.OldSize {
+		err = bb.removeBlocksTruncate(blob, options)
 		if err != nil {
-			log.Err("BlockBlob::TruncateFile : Failed to read small file %s", name, err.Error())
+			log.Err("BlockBlob::TruncateFileUsingBlocks : Failed to Remove Blocks from Blocklist for file %s[%v]",
+				options.Name, err)
+			return err
 		}
 	} else {
-		err = bb.ReadInBuffer(name, 0, size, data, nil)
+		err = bb.createNewBlocksTruncate(blob, options)
 		if err != nil {
-			log.Err("BlockBlob::TruncateFile : Failed to read small file %s", name, err.Error())
+			log.Err("BlockBlob::TruncateFileUsingBlocks : Failed to Create New Blocks for file %s[%v]", options.Name, err)
+			return err
 		}
 	}
-	return data, err
+
+	// Stage and commit the blocks.
+	err = bb.StageAndCommit(options.Name, blob)
+	if err != nil {
+		log.Err("BlockBlob::TruncateFileUsingBlocks : Failed to Stage and Commit blocks for file %s[%v]", options.Name, err)
+		return err
+	}
+
+	return nil
 }
 
 // Write : write data at given offset to a blob
@@ -1409,7 +1554,7 @@ func (bb *BlockBlob) Write(options *internal.WriteFileOptions) error {
 	length := int64(len(options.Data))
 	data := options.Data
 	// case 1: file consists of no blocks (small file)
-	if fileOffsets.SmallFile() {
+	if fileOffsets.HasNoBlocks() {
 		// get all the data
 		oldData, _ := bb.ReadBuffer(name, 0, 0)
 		// update the data with the new data
@@ -1518,18 +1663,37 @@ func (bb *BlockBlob) StageAndCommit(name string, bol *common.BlockOffsetList) er
 	blobMtx := bb.blockLocks.GetLock(name)
 	blobMtx.Lock()
 	defer blobMtx.Unlock()
+
 	blobClient := bb.Container.NewBlockBlobClient(filepath.Join(bb.Config.prefixPath, name))
 	var blockIDList []string
-	var data []byte
-	staged := false
-	for _, blk := range bol.BlockList {
+	var truncBuf, data []byte
+	var truncateBufAllocated = false
+	staged := bol.IsBlockListModified()
+
+	for i, blk := range bol.BlockList {
 		blockIDList = append(blockIDList, blk.Id)
+
 		if blk.Truncated() {
-			data = make([]byte, blk.EndIndex-blk.StartIndex)
+			if !truncateBufAllocated {
+				truncBuf = make([]byte, blk.EndIndex-blk.StartIndex)
+				truncateBufAllocated = true
+			}
+
+			// Match the size of the buffer to the size of the block
+			if len(truncBuf) < int(blk.EndIndex-blk.StartIndex) {
+				// This should not happen since we allocate the buffer once to the max size of block
+				return fmt.Errorf("BlockBlob::StageAndCommit : Truncate buffer size %d is smaller than block size %d for idx: %d, id: %s for blob %s",
+					len(truncBuf), blk.EndIndex-blk.StartIndex, i, blk.Id, name)
+			}
+
+			// Reslice the buffer to the size of the block, as the last block could be smaller than the rest
+			data = truncBuf[:blk.EndIndex-blk.StartIndex]
+
 			blk.Flags.Clear(common.TruncatedBlock)
 		} else {
 			data = blk.Data
 		}
+
 		if blk.Dirty() {
 			_, err := blobClient.StageBlock(context.Background(),
 				blk.Id,
@@ -1543,10 +1707,9 @@ func (bb *BlockBlob) StageAndCommit(name string, bol *common.BlockOffsetList) er
 			}
 			staged = true
 			blk.Flags.Clear(common.DirtyBlock)
-		} else if blk.Removed() {
-			staged = true
 		}
 	}
+
 	if staged {
 		_, err := blobClient.CommitBlockList(context.Background(),
 			blockIDList,
diff --git a/component/azstorage/connection.go b/component/azstorage/connection.go
index 5abc8c9..f63db12 100644
--- a/component/azstorage/connection.go
+++ b/component/azstorage/connection.go
@@ -130,7 +130,7 @@ type AzConnection interface {
 
 	ChangeMod(string, os.FileMode) error
 	ChangeOwner(string, int, int) error
-	TruncateFile(string, int64) error
+	TruncateFile(options internal.TruncateFileOptions) error
 	StageAndCommit(name string, bol *common.BlockOffsetList) error
 
 	GetCommittedBlockList(string) (*internal.CommittedBlockList, error)
diff --git a/component/azstorage/datalake.go b/component/azstorage/datalake.go
index 128984b..a70e6b3 100644
--- a/component/azstorage/datalake.go
+++ b/component/azstorage/datalake.go
@@ -541,8 +541,8 @@ func (dl *Datalake) GetFileBlockOffsets(name string) (*common.BlockOffsetList, e
 	return dl.BlockBlob.GetFileBlockOffsets(name)
 }
 
-func (dl *Datalake) TruncateFile(name string, size int64) error {
-	return dl.BlockBlob.TruncateFile(name, size)
+func (dl *Datalake) TruncateFile(options internal.TruncateFileOptions) error {
+	return dl.BlockBlob.TruncateFile(options)
 }
 
 // ChangeMod : Change mode of a path
diff --git a/component/block_cache/block_cache.go b/component/block_cache/block_cache.go
index 37aaeb8..50465ba 100755
--- a/component/block_cache/block_cache.go
+++ b/component/block_cache/block_cache.go
@@ -1920,6 +1920,21 @@ func (bc *BlockCache) SyncFile(options internal.SyncFileOptions) error {
 	return nil
 }
 
+func (bc *BlockCache) TruncateFile(options internal.TruncateFileOptions) error {
+	log.Trace("BlockCache::TruncateFile : path=%s, size=%d", options.Name, options.NewSize)
+
+	// Set the block size that need to used by the next component
+	options.BlockSize = int64(bc.blockSize)
+
+	err := bc.NextComponent().TruncateFile(options)
+	if err != nil {
+		log.Err("BlockCache::TruncateFile : Failed to truncate file %s: %v", options.Name, err)
+		return err
+	}
+
+	return nil
+}
+
 func (bc *BlockCache) StatFs() (*syscall.Statfs_t, bool, error) {
 	var maxCacheSize uint64
 	if bc.diskSize > 0 {
diff --git a/component/file_cache/file_cache.go b/component/file_cache/file_cache.go
index ae447d9..dc0186d 100644
--- a/component/file_cache/file_cache.go
+++ b/component/file_cache/file_cache.go
@@ -1488,20 +1488,40 @@ func (fc *FileCache) RenameFile(options internal.RenameFileOptions) error {
 
 // TruncateFile: Update the file with its new size.
 func (fc *FileCache) TruncateFile(options internal.TruncateFileOptions) error {
-	log.Trace("FileCache::TruncateFile : name=%s, size=%d", options.Name, options.Size)
+	log.Trace("FileCache::TruncateFile : name=%s, size=%d", options.Name, options.NewSize)
 
 	if fc.diskHighWaterMark != 0 {
 		currSize, err := common.GetUsage(fc.tmpPath)
 		if err != nil {
 			log.Err("FileCache::TruncateFile : error getting current usage of cache [%s]", err.Error())
 		} else {
-			if (currSize + float64(options.Size)) > fc.diskHighWaterMark {
+			if (currSize + float64(options.NewSize)) > fc.diskHighWaterMark {
 				log.Err("FileCache::TruncateFile : cache size limit reached [%f] failed to open %s", fc.maxCacheSize, options.Name)
 				return syscall.ENOSPC
 			}
 		}
 	}
 
+	if options.Handle != nil {
+		// The call is coming from an open handle, so we can just truncate the local file, and the change will be
+		// flushed to storage on close.
+		f := options.Handle.GetFileObject()
+		if f == nil {
+			log.Err("FileCache::TruncateFile : error [couldn't find fd in handle] %s", options.Handle.Path)
+			return syscall.EBADF
+		}
+
+		err := f.Truncate(options.NewSize)
+		if err != nil {
+			log.Err("FileCache::TruncateFile : error truncating file %s [%s]", options.Handle.Path, err.Error())
+			return err
+		}
+
+		options.Handle.Flags.Set(handlemap.HandleFlagDirty)
+
+		return nil
+	}
+
 	flock := fc.fileLocks.Get(options.Name)
 	flock.Lock()
 	defer flock.Unlock()
@@ -1519,8 +1539,8 @@ func (fc *FileCache) TruncateFile(options internal.TruncateFileOptions) error {
 	if err == nil || os.IsExist(err) {
 		fc.policy.CacheValid(localPath)
 
-		if info.Size() != options.Size {
-			err = os.Truncate(localPath, options.Size)
+		if info.Size() != options.NewSize {
+			err = os.Truncate(localPath, options.NewSize)
 			if err != nil {
 				log.Err("FileCache::TruncateFile : error truncating cached file %s [%s]", localPath, err.Error())
 				return err
diff --git a/component/libfuse/libfuse2_handler.go b/component/libfuse/libfuse2_handler.go
index a50bbb8..f7882fb 100644
--- a/component/libfuse/libfuse2_handler.go
+++ b/component/libfuse/libfuse2_handler.go
@@ -794,7 +794,12 @@ func libfuse2_truncate(path *C.char, off C.off_t) C.int {
 
 	log.Trace("Libfuse::libfuse2_truncate : %s size %d", name, off)
 
-	err := fuseFS.NextComponent().TruncateFile(internal.TruncateFileOptions{Name: name, Size: int64(off)})
+	err := fuseFS.NextComponent().TruncateFile(
+		internal.TruncateFileOptions{
+			Name:    name,
+			OldSize: -1,
+			NewSize: int64(off),
+		})
 	if err != nil {
 		log.Err("Libfuse::libfuse2_truncate : error truncating file %s [%s]", name, err.Error())
 		if os.IsNotExist(err) {
diff --git a/component/libfuse/libfuse_handler.go b/component/libfuse/libfuse_handler.go
index 6b39f53..efb68ba 100644
--- a/component/libfuse/libfuse_handler.go
+++ b/component/libfuse/libfuse_handler.go
@@ -855,9 +855,10 @@ func libfuse_truncate(path *C.char, off C.off_t, fi *C.fuse_file_info_t) C.int {
 
 	err := fuseFS.NextComponent().TruncateFile(
 		internal.TruncateFileOptions{
-			Handle: handle,
-			Name:   name,
-			Size:   int64(off),
+			Handle:  handle,
+			Name:    name,
+			OldSize: -1, // upstream components will get the actual size of the file.
+			NewSize: int64(off),
 		})
 
 	if err != nil {
diff --git a/component/loopback/loopback_fs.go b/component/loopback/loopback_fs.go
index 593cb12..dfbcef6 100644
--- a/component/loopback/loopback_fs.go
+++ b/component/loopback/loopback_fs.go
@@ -386,7 +386,7 @@ func (lfs *LoopbackFS) WriteFile(options *internal.WriteFileOptions) (int, error
 func (lfs *LoopbackFS) TruncateFile(options internal.TruncateFileOptions) error {
 	log.Trace("LoopbackFS::TruncateFile : name=%s", options.Name)
 	fsPath := filepath.Join(lfs.path, options.Name)
-	return os.Truncate(fsPath, options.Size)
+	return os.Truncate(fsPath, options.NewSize)
 }
 
 func (lfs *LoopbackFS) FlushFile(options internal.FlushFileOptions) error {
diff --git a/go.mod b/go.mod
index e67603d..f0f5ca8 100755
--- a/go.mod
+++ b/go.mod
@@ -4,7 +4,7 @@ go 1.25.1
 
 require (
 	github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1
-	github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.12.0
+	github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.0
 	github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.2
 	github.com/Azure/azure-sdk-for-go/sdk/storage/azdatalake v1.4.2
 	github.com/JeffreyRichter/enum v0.0.0-20180725232043-2567042f9cda
@@ -46,10 +46,10 @@ require (
 	github.com/spf13/cast v1.10.0 // indirect
 	github.com/subosito/gotenv v1.6.0 // indirect
 	go.yaml.in/yaml/v3 v3.0.4 // indirect
-	golang.org/x/crypto v0.42.0 // indirect
-	golang.org/x/net v0.44.0 // indirect
-	golang.org/x/sys v0.36.0 // indirect
-	golang.org/x/text v0.29.0 // indirect
+	golang.org/x/crypto v0.43.0 // indirect
+	golang.org/x/net v0.46.0 // indirect
+	golang.org/x/sys v0.37.0 // indirect
+	golang.org/x/text v0.30.0 // indirect
 )
 
 replace github.com/spf13/cobra => github.com/gapra-msft/cobra v1.4.1-0.20220411185530-5b83e8ba06dd
diff --git a/go.sum b/go.sum
index 2be907e..618d673 100644
--- a/go.sum
+++ b/go.sum
@@ -1,7 +1,7 @@
 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1 h1:5YTBM8QDVIBN3sxBil89WfdAAqDZbyJTgh688DSxX5w=
 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw=
-github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.12.0 h1:wL5IEG5zb7BVv1Kv0Xm92orq+5hB5Nipn3B5tn4Rqfk=
-github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.12.0/go.mod h1:J7MUC/wtRpfGVbQ5sIItY5/FuVWmvzlY21WAOfQnq/I=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.0 h1:KpMC6LFL7mqpExyMC9jVOYRiVhLmamjeZfRsUpB7l4s=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.0/go.mod h1:J7MUC/wtRpfGVbQ5sIItY5/FuVWmvzlY21WAOfQnq/I=
 github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY=
 github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8=
 github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA=
@@ -96,14 +96,14 @@ go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
 go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
-golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
+golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
+golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
-golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
-golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
+golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
+golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -112,13 +112,13 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
-golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
+golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
-golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
+golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
+golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
diff --git a/internal/component_options.go b/internal/component_options.go
index 46a2e2f..9044302 100644
--- a/internal/component_options.go
+++ b/internal/component_options.go
@@ -127,9 +127,12 @@ type GetFileBlockOffsetsOptions struct {
 }
 
 type TruncateFileOptions struct {
-	Handle *handlemap.Handle
-	Name   string
-	Size   int64
+	Handle  *handlemap.Handle
+	Name    string
+	OldSize int64
+	NewSize int64
+	// This is equivalent to the storage block Size.
+	BlockSize int64
 }
 
 type CopyToFileOptions struct {



Test patch:
diff --git a/component/attr_cache/attr_cache_test.go b/component/attr_cache/attr_cache_test.go
index 22b92d0..efa4b22 100644
--- a/component/attr_cache/attr_cache_test.go
+++ b/component/attr_cache/attr_cache_test.go
@@ -792,7 +792,7 @@ func (suite *attrCacheTestSuite) TestTruncateFile() {
 	path := "a"
 	size := 1024
 
-	options := internal.TruncateFileOptions{Name: path, Size: int64(size)}
+	options := internal.TruncateFileOptions{Name: path, NewSize: int64(size)}
 
 	// Error
 	suite.mock.EXPECT().TruncateFile(options).Return(errors.New("Failed to truncate a file"))
diff --git a/component/azstorage/block_blob_test.go b/component/azstorage/block_blob_test.go
index 9566070..f4d08ff 100644
--- a/component/azstorage/block_blob_test.go
+++ b/component/azstorage/block_blob_test.go
@@ -74,6 +74,7 @@ import (
 var ctx = context.Background()
 
 const MB = 1024 * 1024
+const GB = 1024 * MB
 
 // A UUID representation compliant with specification in RFC 4122 document.
 type uuid [16]byte
@@ -995,7 +996,7 @@ func (s *blockBlobTestSuite) TestOpenFileSize() {
 	name := generateFileName()
 	size := 10
 	s.az.CreateFile(internal.CreateFileOptions{Name: name})
-	s.az.TruncateFile(internal.TruncateFileOptions{Name: name, Size: int64(size)})
+	s.az.TruncateFile(internal.TruncateFileOptions{Name: name, OldSize: -1, NewSize: int64(size)})
 
 	h, err := s.az.OpenFile(internal.OpenFileOptions{Name: name})
 	s.assert.NoError(err)
@@ -1345,7 +1346,7 @@ func (s *blockBlobTestSuite) TestTruncateSmallFileSmaller() {
 	truncatedLength := 5
 	s.az.WriteFile(&internal.WriteFileOptions{Handle: h, Offset: 0, Data: data})
 
-	err := s.az.TruncateFile(internal.TruncateFileOptions{Name: name, Size: int64(truncatedLength)})
+	err := s.az.TruncateFile(internal.TruncateFileOptions{Name: name, OldSize: -1, NewSize: int64(truncatedLength)})
 	s.assert.NoError(err)
 
 	// Blob should have updated data
@@ -1368,7 +1369,7 @@ func (s *blockBlobTestSuite) TestTruncateEmptyFileToLargeSize() {
 	s.assert.NotNil(h)
 
 	blobSize := int64((1 * common.GbToBytes) + 13)
-	err := s.az.TruncateFile(internal.TruncateFileOptions{Name: name, Size: blobSize})
+	err := s.az.TruncateFile(internal.TruncateFileOptions{Name: name, OldSize: -1, NewSize: blobSize})
 	s.assert.NoError(err)
 
 	props, err := s.az.GetAttr(internal.GetAttrOptions{Name: name})
@@ -1394,7 +1395,7 @@ func (s *blockBlobTestSuite) TestTruncateChunkedFileSmaller() {
 	})
 	s.assert.NoError(err)
 
-	err = s.az.TruncateFile(internal.TruncateFileOptions{Name: name, Size: int64(truncatedLength)})
+	err = s.az.TruncateFile(internal.TruncateFileOptions{Name: name, OldSize: -1, NewSize: int64(truncatedLength)})
 	s.assert.NoError(err)
 
 	// Blob should have updated data
@@ -1419,7 +1420,7 @@ func (s *blockBlobTestSuite) TestTruncateSmallFileEqual() {
 	truncatedLength := 9
 	s.az.WriteFile(&internal.WriteFileOptions{Handle: h, Offset: 0, Data: data})
 
-	err := s.az.TruncateFile(internal.TruncateFileOptions{Name: name, Size: int64(truncatedLength)})
+	err := s.az.TruncateFile(internal.TruncateFileOptions{Name: name, OldSize: -1, NewSize: int64(truncatedLength)})
 	s.assert.NoError(err)
 
 	// Blob should have updated data
@@ -1448,7 +1449,7 @@ func (s *blockBlobTestSuite) TestTruncateChunkedFileEqual() {
 	})
 	s.assert.NoError(err)
 
-	err = s.az.TruncateFile(internal.TruncateFileOptions{Name: name, Size: int64(truncatedLength)})
+	err = s.az.TruncateFile(internal.TruncateFileOptions{Name: name, OldSize: -1, NewSize: int64(truncatedLength)})
 	s.assert.NoError(err)
 
 	// Blob should have updated data
@@ -1473,7 +1474,7 @@ func (s *blockBlobTestSuite) TestTruncateSmallFileBigger() {
 	truncatedLength := 15
 	s.az.WriteFile(&internal.WriteFileOptions{Handle: h, Offset: 0, Data: data})
 
-	err := s.az.TruncateFile(internal.TruncateFileOptions{Name: name, Size: int64(truncatedLength)})
+	err := s.az.TruncateFile(internal.TruncateFileOptions{Name: name, OldSize: -1, NewSize: int64(truncatedLength)})
 	s.assert.NoError(err)
 
 	// Blob should have updated data
@@ -1502,7 +1503,7 @@ func (s *blockBlobTestSuite) TestTruncateChunkedFileBigger() {
 	})
 	s.assert.NoError(err)
 
-	err = s.az.TruncateFile(internal.TruncateFileOptions{Name: name, Size: int64(truncatedLength)})
+	err = s.az.TruncateFile(internal.TruncateFileOptions{Name: name, OldSize: -1, NewSize: int64(truncatedLength)})
 	s.assert.NoError(err)
 
 	// Blob should have updated data
@@ -1522,7 +1523,7 @@ func (s *blockBlobTestSuite) TestTruncateFileError() {
 	// Setup
 	name := generateFileName()
 
-	err := s.az.TruncateFile(internal.TruncateFileOptions{Name: name})
+	err := s.az.TruncateFile(internal.TruncateFileOptions{Name: name, OldSize: -1})
 	s.assert.Error(err)
 	s.assert.EqualValues(syscall.ENOENT, err)
 }
@@ -2342,8 +2343,7 @@ func (s *blockBlobTestSuite) TestGetFileBlockOffsetsSmallFile() {
 	offsetList, err := s.az.GetFileBlockOffsets(internal.GetFileBlockOffsetsOptions{Name: name})
 	s.assert.NoError(err)
 	s.assert.Empty(offsetList.BlockList)
-	s.assert.True(offsetList.SmallFile())
-	s.assert.EqualValues(0, offsetList.BlockIdLength)
+	s.assert.True(offsetList.HasNoBlocks())
 }
 
 func (s *blockBlobTestSuite) TestGetFileBlockOffsetsChunkedFile() {
@@ -2539,8 +2539,8 @@ func (s *blockBlobTestSuite) TestFlushFileAppendBlocksEmptyFile() {
 		Data:       data3,
 	}
 	blk3.Flags.Set(common.DirtyBlock)
-	h.CacheObj.BlockList = append(h.CacheObj.BlockList, blk1, blk2, blk3)
-	bol.Flags.Clear(common.SmallFile)
+	h.CacheObj.BlockOffsetList.BlockList = append(h.CacheObj.BlockOffsetList.BlockList, blk1, blk2, blk3)
+	bol.Flags.Clear(common.BlobFlagHasNoBlocks)
 
 	err := s.az.FlushFile(internal.FlushFileOptions{Handle: h})
 	s.assert.NoError(err)
@@ -2602,8 +2602,8 @@ func (s *blockBlobTestSuite) TestFlushFileAppendBlocksChunkedFile() {
 		Data:       data3,
 	}
 	blk3.Flags.Set(common.DirtyBlock)
-	h.CacheObj.BlockList = append(h.CacheObj.BlockList, blk1, blk2, blk3)
-	bol.Flags.Clear(common.SmallFile)
+	h.CacheObj.BlockOffsetList.BlockList = append(h.CacheObj.BlockOffsetList.BlockList, blk1, blk2, blk3)
+	bol.Flags.Clear(common.BlobFlagHasNoBlocks)
 
 	err = s.az.FlushFile(internal.FlushFileOptions{Handle: h})
 	s.assert.NoError(err)
@@ -2652,8 +2652,8 @@ func (s *blockBlobTestSuite) TestFlushFileTruncateBlocksEmptyFile() {
 	}
 	blk3.Flags.Set(common.TruncatedBlock)
 	blk3.Flags.Set(common.DirtyBlock)
-	h.CacheObj.BlockList = append(h.CacheObj.BlockList, blk1, blk2, blk3)
-	bol.Flags.Clear(common.SmallFile)
+	h.CacheObj.BlockOffsetList.BlockList = append(h.CacheObj.BlockOffsetList.BlockList, blk1, blk2, blk3)
+	bol.Flags.Clear(common.BlobFlagHasNoBlocks)
 
 	err := s.az.FlushFile(internal.FlushFileOptions{Handle: h})
 	s.assert.NoError(err)
@@ -2708,8 +2708,8 @@ func (s *blockBlobTestSuite) TestFlushFileTruncateBlocksChunkedFile() {
 	}
 	blk3.Flags.Set(common.TruncatedBlock)
 	blk3.Flags.Set(common.DirtyBlock)
-	h.CacheObj.BlockList = append(h.CacheObj.BlockList, blk1, blk2, blk3)
-	bol.Flags.Clear(common.SmallFile)
+	h.CacheObj.BlockOffsetList.BlockList = append(h.CacheObj.BlockOffsetList.BlockList, blk1, blk2, blk3)
+	bol.Flags.Clear(common.BlobFlagHasNoBlocks)
 
 	err = s.az.FlushFile(internal.FlushFileOptions{Handle: h})
 	s.assert.NoError(err)
@@ -2759,8 +2759,8 @@ func (s *blockBlobTestSuite) TestFlushFileAppendAndTruncateBlocksEmptyFile() {
 	}
 	blk3.Flags.Set(common.DirtyBlock)
 	blk3.Flags.Set(common.TruncatedBlock)
-	h.CacheObj.BlockList = append(h.CacheObj.BlockList, blk1, blk2, blk3)
-	bol.Flags.Clear(common.SmallFile)
+	h.CacheObj.BlockOffsetList.BlockList = append(h.CacheObj.BlockOffsetList.BlockList, blk1, blk2, blk3)
+	bol.Flags.Clear(common.BlobFlagHasNoBlocks)
 
 	err := s.az.FlushFile(internal.FlushFileOptions{Handle: h})
 	s.assert.NoError(err)
@@ -2819,8 +2819,8 @@ func (s *blockBlobTestSuite) TestFlushFileAppendAndTruncateBlocksChunkedFile() {
 	}
 	blk3.Flags.Set(common.DirtyBlock)
 	blk3.Flags.Set(common.TruncatedBlock)
-	h.CacheObj.BlockList = append(h.CacheObj.BlockList, blk1, blk2, blk3)
-	bol.Flags.Clear(common.SmallFile)
+	h.CacheObj.BlockOffsetList.BlockList = append(h.CacheObj.BlockOffsetList.BlockList, blk1, blk2, blk3)
+	bol.Flags.Clear(common.BlobFlagHasNoBlocks)
 
 	err = s.az.FlushFile(internal.FlushFileOptions{Handle: h})
 	s.assert.NoError(err)
@@ -3553,7 +3553,7 @@ func (suite *blockBlobTestSuite) UtilityFunctionTestTruncateFileToSmaller(size i
 	data := make([]byte, size)
 	suite.az.WriteFile(&internal.WriteFileOptions{Handle: h, Offset: 0, Data: data})
 
-	err = suite.az.TruncateFile(internal.TruncateFileOptions{Name: name, Size: int64(truncatedLength)})
+	err = suite.az.TruncateFile(internal.TruncateFileOptions{Name: name, OldSize: -1, NewSize: int64(truncatedLength)})
 	suite.assert.NoError(err)
 
 	// Blob should have updated data
@@ -3585,7 +3585,7 @@ func (suite *blockBlobTestSuite) UtilityFunctionTruncateFileToLarger(size int, t
 	data := make([]byte, size)
 	suite.az.WriteFile(&internal.WriteFileOptions{Handle: h, Offset: 0, Data: data})
 
-	err = suite.az.TruncateFile(internal.TruncateFileOptions{Name: name, Size: int64(truncatedLength)})
+	err = suite.az.TruncateFile(internal.TruncateFileOptions{Name: name, OldSize: -1, NewSize: int64(truncatedLength)})
 	suite.assert.NoError(err)
 
 	// Blob should have updated data
diff --git a/component/azstorage/datalake_test.go b/component/azstorage/datalake_test.go
index 05fbb58..bdafb6f 100644
--- a/component/azstorage/datalake_test.go
+++ b/component/azstorage/datalake_test.go
@@ -1233,7 +1233,7 @@ func (s *datalakeTestSuite) TestOpenFileSize() {
 	name := generateFileName()
 	size := 10
 	s.az.CreateFile(internal.CreateFileOptions{Name: name})
-	s.az.TruncateFile(internal.TruncateFileOptions{Name: name, Size: int64(size)})
+	s.az.TruncateFile(internal.TruncateFileOptions{Name: name, OldSize: -1, NewSize: int64(size)})
 
 	h, err := s.az.OpenFile(internal.OpenFileOptions{Name: name})
 	s.assert.NoError(err)
@@ -1590,7 +1590,7 @@ func (s *datalakeTestSuite) TestTruncateSmallFileSmaller() {
 	truncatedLength := 5
 	s.az.WriteFile(&internal.WriteFileOptions{Handle: h, Offset: 0, Data: data})
 
-	err := s.az.TruncateFile(internal.TruncateFileOptions{Name: name, Size: int64(truncatedLength)})
+	err := s.az.TruncateFile(internal.TruncateFileOptions{Name: name, OldSize: -1, NewSize: int64(truncatedLength)})
 	s.assert.NoError(err)
 
 	// Blob should have updated data
@@ -1620,7 +1620,7 @@ func (s *datalakeTestSuite) TestTruncateChunkedFileSmaller() {
 		})
 	s.assert.NoError(err)
 
-	err = s.az.TruncateFile(internal.TruncateFileOptions{Name: name, Size: int64(truncatedLength)})
+	err = s.az.TruncateFile(internal.TruncateFileOptions{Name: name, OldSize: -1, NewSize: int64(truncatedLength)})
 	s.assert.NoError(err)
 
 	// Blob should have updated data
@@ -1644,7 +1644,7 @@ func (s *datalakeTestSuite) TestTruncateSmallFileEqual() {
 	truncatedLength := 9
 	s.az.WriteFile(&internal.WriteFileOptions{Handle: h, Offset: 0, Data: data})
 
-	err := s.az.TruncateFile(internal.TruncateFileOptions{Name: name, Size: int64(truncatedLength)})
+	err := s.az.TruncateFile(internal.TruncateFileOptions{Name: name, OldSize: -1, NewSize: int64(truncatedLength)})
 	s.assert.NoError(err)
 
 	// Blob should have updated data
@@ -1673,7 +1673,7 @@ func (s *datalakeTestSuite) TestTruncateChunkedFileEqual() {
 		})
 	s.assert.NoError(err)
 
-	err = s.az.TruncateFile(internal.TruncateFileOptions{Name: name, Size: int64(truncatedLength)})
+	err = s.az.TruncateFile(internal.TruncateFileOptions{Name: name, OldSize: -1, NewSize: int64(truncatedLength)})
 	s.assert.NoError(err)
 
 	// Blob should have updated data
@@ -1697,7 +1697,7 @@ func (s *datalakeTestSuite) TestTruncateSmallFileBigger() {
 	truncatedLength := 15
 	s.az.WriteFile(&internal.WriteFileOptions{Handle: h, Offset: 0, Data: data})
 
-	err := s.az.TruncateFile(internal.TruncateFileOptions{Name: name, Size: int64(truncatedLength)})
+	err := s.az.TruncateFile(internal.TruncateFileOptions{Name: name, OldSize: -1, NewSize: int64(truncatedLength)})
 	s.assert.NoError(err)
 
 	// Blob should have updated data
@@ -1726,7 +1726,7 @@ func (s *datalakeTestSuite) TestTruncateChunkedFileBigger() {
 		})
 	s.assert.NoError(err)
 
-	s.az.TruncateFile(internal.TruncateFileOptions{Name: name, Size: int64(truncatedLength)})
+	s.az.TruncateFile(internal.TruncateFileOptions{Name: name, OldSize: -1, NewSize: int64(truncatedLength)})
 	s.assert.NoError(err)
 
 	// Blob should have updated data
@@ -1745,7 +1745,7 @@ func (s *datalakeTestSuite) TestTruncateFileError() {
 	// Setup
 	name := generateFileName()
 
-	err := s.az.TruncateFile(internal.TruncateFileOptions{Name: name})
+	err := s.az.TruncateFile(internal.TruncateFileOptions{Name: name, OldSize: -1})
 	s.assert.Error(err)
 	s.assert.EqualValues(syscall.ENOENT, err)
 }
@@ -2018,10 +2018,9 @@ func (s *datalakeTestSuite) TestGetFileBlockOffsetsSmallFile() {
 
 	// GetFileBlockOffsets
 	offsetList, err := s.az.GetFileBlockOffsets(internal.GetFileBlockOffsetsOptions{Name: name})
-	s.assert.NoError(err)
-	s.assert.Empty(offsetList.BlockList)
-	s.assert.True(offsetList.SmallFile())
-	s.assert.EqualValues(0, offsetList.BlockIdLength)
+	s.assert.Nil(err)
+	s.assert.Len(offsetList.BlockList, 0)
+	s.assert.True(offsetList.HasNoBlocks())
 }
 
 func (s *datalakeTestSuite) TestGetFileBlockOffsetsChunkedFile() {
@@ -2233,8 +2232,8 @@ func (s *datalakeTestSuite) TestFlushFileAppendBlocksEmptyFile() {
 		Data:       data3,
 	}
 	blk3.Flags.Set(common.DirtyBlock)
-	h.CacheObj.BlockList = append(h.CacheObj.BlockList, blk1, blk2, blk3)
-	bol.Flags.Clear(common.SmallFile)
+	h.CacheObj.BlockOffsetList.BlockList = append(h.CacheObj.BlockOffsetList.BlockList, blk1, blk2, blk3)
+	bol.Flags.Clear(common.BlobFlagHasNoBlocks)
 
 	err := s.az.FlushFile(internal.FlushFileOptions{Handle: h})
 	s.assert.NoError(err)
@@ -2297,8 +2296,8 @@ func (s *datalakeTestSuite) TestFlushFileAppendBlocksChunkedFile() {
 		Data:       data3,
 	}
 	blk3.Flags.Set(common.DirtyBlock)
-	h.CacheObj.BlockList = append(h.CacheObj.BlockList, blk1, blk2, blk3)
-	bol.Flags.Clear(common.SmallFile)
+	h.CacheObj.BlockOffsetList.BlockList = append(h.CacheObj.BlockOffsetList.BlockList, blk1, blk2, blk3)
+	bol.Flags.Clear(common.BlobFlagHasNoBlocks)
 
 	err = s.az.FlushFile(internal.FlushFileOptions{Handle: h})
 	s.assert.NoError(err)
@@ -2347,8 +2346,8 @@ func (s *datalakeTestSuite) TestFlushFileTruncateBlocksEmptyFile() {
 	}
 	blk3.Flags.Set(common.TruncatedBlock)
 	blk3.Flags.Set(common.DirtyBlock)
-	h.CacheObj.BlockList = append(h.CacheObj.BlockList, blk1, blk2, blk3)
-	bol.Flags.Clear(common.SmallFile)
+	h.CacheObj.BlockOffsetList.BlockList = append(h.CacheObj.BlockOffsetList.BlockList, blk1, blk2, blk3)
+	bol.Flags.Clear(common.BlobFlagHasNoBlocks)
 
 	err := s.az.FlushFile(internal.FlushFileOptions{Handle: h})
 	s.assert.NoError(err)
@@ -2404,8 +2403,8 @@ func (s *datalakeTestSuite) TestFlushFileTruncateBlocksChunkedFile() {
 	}
 	blk3.Flags.Set(common.TruncatedBlock)
 	blk3.Flags.Set(common.DirtyBlock)
-	h.CacheObj.BlockList = append(h.CacheObj.BlockList, blk1, blk2, blk3)
-	bol.Flags.Clear(common.SmallFile)
+	h.CacheObj.BlockOffsetList.BlockList = append(h.CacheObj.BlockOffsetList.BlockList, blk1, blk2, blk3)
+	bol.Flags.Clear(common.BlobFlagHasNoBlocks)
 
 	err = s.az.FlushFile(internal.FlushFileOptions{Handle: h})
 	s.assert.NoError(err)
@@ -2455,8 +2454,8 @@ func (s *datalakeTestSuite) TestFlushFileAppendAndTruncateBlocksEmptyFile() {
 	}
 	blk3.Flags.Set(common.DirtyBlock)
 	blk3.Flags.Set(common.TruncatedBlock)
-	h.CacheObj.BlockList = append(h.CacheObj.BlockList, blk1, blk2, blk3)
-	bol.Flags.Clear(common.SmallFile)
+	h.CacheObj.BlockOffsetList.BlockList = append(h.CacheObj.BlockOffsetList.BlockList, blk1, blk2, blk3)
+	bol.Flags.Clear(common.BlobFlagHasNoBlocks)
 
 	err := s.az.FlushFile(internal.FlushFileOptions{Handle: h})
 	s.assert.NoError(err)
@@ -2516,8 +2515,8 @@ func (s *datalakeTestSuite) TestFlushFileAppendAndTruncateBlocksChunkedFile() {
 	}
 	blk3.Flags.Set(common.DirtyBlock)
 	blk3.Flags.Set(common.TruncatedBlock)
-	h.CacheObj.BlockList = append(h.CacheObj.BlockList, blk1, blk2, blk3)
-	bol.Flags.Clear(common.SmallFile)
+	h.CacheObj.BlockOffsetList.BlockList = append(h.CacheObj.BlockOffsetList.BlockList, blk1, blk2, blk3)
+	bol.Flags.Clear(common.BlobFlagHasNoBlocks)
 
 	err = s.az.FlushFile(internal.FlushFileOptions{Handle: h})
 	s.assert.NoError(err)
diff --git a/component/file_cache/file_cache_test.go b/component/file_cache/file_cache_test.go
index 833cf42..6510efc 100644
--- a/component/file_cache/file_cache_test.go
+++ b/component/file_cache/file_cache_test.go
@@ -408,11 +408,11 @@ func (suite *fileCacheTestSuite) TestReadDirCase3() {
 	suite.fileCache.CreateDir(internal.CreateDirOptions{Name: subdir, Mode: 0777})
 	// By default createEmptyFile is false, so we will not create these files in storage until they are closed.
 	suite.fileCache.CreateFile(internal.CreateFileOptions{Name: file1, Mode: 0777})
-	suite.fileCache.TruncateFile(internal.TruncateFileOptions{Name: file1, Size: 1024})
+	suite.fileCache.TruncateFile(internal.TruncateFileOptions{Name: file1, NewSize: 1024})
 	suite.fileCache.CreateFile(internal.CreateFileOptions{Name: file2, Mode: 0777})
-	suite.fileCache.TruncateFile(internal.TruncateFileOptions{Name: file2, Size: 1024})
+	suite.fileCache.TruncateFile(internal.TruncateFileOptions{Name: file2, NewSize: 1024})
 	suite.fileCache.CreateFile(internal.CreateFileOptions{Name: file3, Mode: 0777})
-	suite.fileCache.TruncateFile(internal.TruncateFileOptions{Name: file3, Size: 1024})
+	suite.fileCache.TruncateFile(internal.TruncateFileOptions{Name: file3, NewSize: 1024})
 	// Create the files in fake_storage and simulate different sizes
 	suite.loopback.CreateFile(internal.CreateFileOptions{Name: file1, Mode: 0777}) // Length is default 0
 	suite.loopback.CreateFile(internal.CreateFileOptions{Name: file2, Mode: 0777})
@@ -456,17 +456,17 @@ func (suite *fileCacheTestSuite) TestReadDirMixed() {
 
 	// By default createEmptyFile is false, so we will not create these files in storage until they are closed.
 	suite.fileCache.CreateFile(internal.CreateFileOptions{Name: file2, Mode: 0777})
-	suite.fileCache.TruncateFile(internal.TruncateFileOptions{Name: file2, Size: 1024})
+	suite.fileCache.TruncateFile(internal.TruncateFileOptions{Name: file2, NewSize: 1024})
 	suite.fileCache.CreateFile(internal.CreateFileOptions{Name: file3, Mode: 0777})
-	suite.fileCache.TruncateFile(internal.TruncateFileOptions{Name: file3, Size: 1024})
+	suite.fileCache.TruncateFile(internal.TruncateFileOptions{Name: file3, NewSize: 1024})
 
 	// Create the files in fake_storage and simulate different sizes
 	suite.loopback.CreateFile(internal.CreateFileOptions{Name: file1, Mode: 0777}) // Length is default 0
 	suite.loopback.CreateFile(internal.CreateFileOptions{Name: file3, Mode: 0777})
 
 	suite.loopback.CreateFile(internal.CreateFileOptions{Name: file4, Mode: 0777})
-	suite.fileCache.TruncateFile(internal.TruncateFileOptions{Name: file4, Size: 1024})
-	suite.fileCache.TruncateFile(internal.TruncateFileOptions{Name: file4, Size: 0})
+	suite.fileCache.TruncateFile(internal.TruncateFileOptions{Name: file4, NewSize: 1024})
+	suite.fileCache.TruncateFile(internal.TruncateFileOptions{Name: file4, NewSize: 0})
 
 	// Read the Directory
 	dir, err := suite.fileCache.ReadDir(internal.ReadDirOptions{Name: name})
@@ -1193,7 +1193,7 @@ func (suite *fileCacheTestSuite) TestGetAttrCase3() {
 	file := "file26"
 	// By default createEmptyFile is false, so we will not create these files in storage until they are closed.
 	suite.fileCache.CreateFile(internal.CreateFileOptions{Name: file, Mode: 0777})
-	suite.fileCache.TruncateFile(internal.TruncateFileOptions{Name: file, Size: 1024})
+	suite.fileCache.TruncateFile(internal.TruncateFileOptions{Name: file, NewSize: 1024})
 	// Create the files in fake_storage and simulate different sizes
 	//suite.loopback.CreateFile(internal.CreateFileOptions{Name: file, Mode: 0777}) // Length is default 0
 
@@ -1446,7 +1446,7 @@ func (suite *fileCacheTestSuite) TestTruncateFileNotInCache() {
 
 	// Chmod
 	size := 1024
-	err = suite.fileCache.TruncateFile(internal.TruncateFileOptions{Name: path, Size: int64(size)})
+	err = suite.fileCache.TruncateFile(internal.TruncateFileOptions{Name: path, NewSize: int64(size)})
 	suite.assert.NoError(err)
 
 	// Path in fake storage should be updated
@@ -1471,7 +1471,7 @@ func (suite *fileCacheTestSuite) TestTruncateFileInCache() {
 
 	// Chmod
 	size := 1024
-	err = suite.fileCache.TruncateFile(internal.TruncateFileOptions{Name: path, Size: int64(size)})
+	err = suite.fileCache.TruncateFile(internal.TruncateFileOptions{Name: path, NewSize: int64(size)})
 	suite.assert.NoError(err)
 	// Path in fake storage and file cache should be updated
 	info, _ := os.Stat(suite.cache_path + "/" + path)
@@ -1489,7 +1489,7 @@ func (suite *fileCacheTestSuite) TestTruncateFileCase2() {
 	suite.fileCache.CreateFile(internal.CreateFileOptions{Name: path, Mode: 0666})
 
 	size := 1024
-	err := suite.fileCache.TruncateFile(internal.TruncateFileOptions{Name: path, Size: int64(size)})
+	err := suite.fileCache.TruncateFile(internal.TruncateFileOptions{Name: path, NewSize: int64(size)})
 	suite.assert.NoError(err)
 
 	// Path should be in the file cache and size should be updated
@@ -1902,11 +1902,11 @@ func (suite *fileCacheTestSuite) TestHardLimitOnSize() {
 	suite.assert.NoError(err)
 
 	// try opening small file
-	err = suite.fileCache.TruncateFile(internal.TruncateFileOptions{Name: pathsmall, Size: 1 * MB})
+	err = suite.fileCache.TruncateFile(internal.TruncateFileOptions{Name: pathsmall, NewSize: 1 * MB})
 	suite.assert.NoError(err)
 
 	// try opening small file
-	err = suite.fileCache.TruncateFile(internal.TruncateFileOptions{Name: pathsmall, Size: 3 * MB})
+	err = suite.fileCache.TruncateFile(internal.TruncateFileOptions{Name: pathsmall, NewSize: 3 * MB})
 	suite.assert.Error(err)
 }
 
diff --git a/component/libfuse/libfuse2_handler_test_wrapper.go b/component/libfuse/libfuse2_handler_test_wrapper.go
index 2b789f7..484ba09 100644
--- a/component/libfuse/libfuse2_handler_test_wrapper.go
+++ b/component/libfuse/libfuse2_handler_test_wrapper.go
@@ -395,7 +395,7 @@ func testTruncate(suite *libfuseTestSuite) {
 	path := C.CString("/" + name)
 	defer C.free(unsafe.Pointer(path))
 	size := int64(1024)
-	options := internal.TruncateFileOptions{Name: name, Size: size}
+	options := internal.TruncateFileOptions{Name: name, OldSize: -1, NewSize: size}
 	suite.mock.EXPECT().TruncateFile(options).Return(nil)
 
 	err := libfuse2_truncate(path, C.long(size))
@@ -408,7 +408,7 @@ func testTruncateError(suite *libfuseTestSuite) {
 	path := C.CString("/" + name)
 	defer C.free(unsafe.Pointer(path))
 	size := int64(1024)
-	options := internal.TruncateFileOptions{Name: name, Size: size}
+	options := internal.TruncateFileOptions{Name: name, OldSize: -1, NewSize: size}
 	suite.mock.EXPECT().TruncateFile(options).Return(errors.New("failed to truncate file"))
 
 	err := libfuse2_truncate(path, C.long(size))
diff --git a/component/libfuse/libfuse_handler_test_wrapper.go b/component/libfuse/libfuse_handler_test_wrapper.go
index 1e6e5a8..87301a1 100644
--- a/component/libfuse/libfuse_handler_test_wrapper.go
+++ b/component/libfuse/libfuse_handler_test_wrapper.go
@@ -373,7 +373,7 @@ func testTruncate(suite *libfuseTestSuite) {
 	path := C.CString("/" + name)
 	defer C.free(unsafe.Pointer(path))
 	size := int64(1024)
-	options := internal.TruncateFileOptions{Name: name, Size: size}
+	options := internal.TruncateFileOptions{Name: name, OldSize: -1, NewSize: size}
 	suite.mock.EXPECT().TruncateFile(options).Return(nil)
 
 	err := libfuse_truncate(path, C.long(size), nil)
@@ -386,7 +386,7 @@ func testTruncateError(suite *libfuseTestSuite) {
 	path := C.CString("/" + name)
 	defer C.free(unsafe.Pointer(path))
 	size := int64(1024)
-	options := internal.TruncateFileOptions{Name: name, Size: size}
+	options := internal.TruncateFileOptions{Name: name, OldSize: -1, NewSize: size}
 	suite.mock.EXPECT().TruncateFile(options).Return(errors.New("failed to truncate file"))
 
 	err := libfuse_truncate(path, C.long(size), nil)
@@ -405,7 +405,7 @@ func testFTruncate(suite *libfuseTestSuite) {
 	fi := C.fuse_file_info_t{}
 	fi.fh = C.ulong(uintptr(unsafe.Pointer(ret_val)))
 
-	options := internal.TruncateFileOptions{Handle: handle, Name: name, Size: size}
+	options := internal.TruncateFileOptions{Handle: handle, Name: name, OldSize: -1, NewSize: size}
 	suite.mock.EXPECT().TruncateFile(options).Return(nil)
 
 	err := libfuse_truncate(path, C.long(size), &fi)
@@ -424,7 +424,7 @@ func testFTruncateError(suite *libfuseTestSuite) {
 	fi := C.fuse_file_info_t{}
 	fi.fh = C.ulong(uintptr(unsafe.Pointer(ret_val)))
 
-	options := internal.TruncateFileOptions{Handle: handle, Name: name, Size: size}
+	options := internal.TruncateFileOptions{Handle: handle, Name: name, OldSize: -1, NewSize: size}
 	suite.mock.EXPECT().TruncateFile(options).Return(errors.New("failed to truncate file"))
 
 	err := libfuse_truncate(path, C.long(size), &fi)
diff --git a/component/loopback/loopback_fs_test.go b/component/loopback/loopback_fs_test.go
index 43a0a6e..3e8c7dc 100644
--- a/component/loopback/loopback_fs_test.go
+++ b/component/loopback/loopback_fs_test.go
@@ -262,7 +262,7 @@ func (suite *LoopbackFSTestSuite) TestTruncateFile() {
 	assert.NoError(err, "TruncateFile: failed to open file")
 	assert.NotNil(handle)
 
-	err = suite.lfs.TruncateFile(internal.TruncateFileOptions{Name: fileLorem, Size: 0})
+	err = suite.lfs.TruncateFile(internal.TruncateFileOptions{Name: fileLorem, NewSize: 0})
 	assert.NoError(err)
 	info, err := os.Stat(filepath.Join(testPath, fileLorem))
 	assert.NoError(err, "TruncateFile: cannot stat file")
diff --git a/test/e2e_tests/data_validation_test.go b/test/e2e_tests/data_validation_test.go
index 9da6f1e..71e5a39 100644
--- a/test/e2e_tests/data_validation_test.go
+++ b/test/e2e_tests/data_validation_test.go
@@ -142,12 +142,90 @@ func (suite *dataValidationTestSuite) computeMD5(filePath string) []byte {
 	return hash.Sum(nil)
 }
 
-func (suite *dataValidationTestSuite) validateData(localFilePath string, remoteFilePath string) {
+func (suite *dataValidationTestSuite) helperValidateFileContent(localFilePath string, remoteFilePath string) {
+	// check if file sizes are same
+	localFileInfo, err := os.Stat(localFilePath)
+	suite.NoError(err)
+	remoteFileInfo, err := os.Stat(remoteFilePath)
+	suite.NoError(err)
+	suite.Equal(localFileInfo.Size(), remoteFileInfo.Size())
+
 	localMD5sum := suite.computeMD5(localFilePath)
 	remoteMD5sum := suite.computeMD5(remoteFilePath)
 	suite.Equal(localMD5sum, remoteMD5sum)
 }
 
+func (suite *dataValidationTestSuite) helperCreateFile(localFilePath string, remoteFilePath string, size int64) {
+	buffer := make([]byte, 1*1024*1024)
+	rand.Read(buffer)
+
+	writeFile := func(file *os.File) {
+		originalSize := size
+		for originalSize > 0 {
+			bytesToWrite := min(int64(len(buffer)), originalSize)
+			n, err := file.Write(buffer[0:bytesToWrite])
+			suite.Equal(int(bytesToWrite), n)
+			ok := suite.NoError(err)
+			if !ok {
+				break
+			}
+			originalSize -= int64(n)
+		}
+	}
+
+	localFile, err := os.Create(localFilePath)
+	suite.NoError(err)
+	writeFile(localFile)
+	err = localFile.Close()
+	suite.NoError(err)
+
+	remoteFile, err := os.Create(remoteFilePath)
+	suite.NoError(err)
+	writeFile(remoteFile)
+	err = remoteFile.Close()
+	suite.NoError(err)
+}
+
+func (suite *dataValidationTestSuite) helperTruncateFile(localFilePath string, remoteFilePath string, size int64) {
+	srcFile, err := os.OpenFile(localFilePath, os.O_RDWR, 0666)
+	suite.NoError(err)
+	err = srcFile.Truncate(size)
+	suite.NoError(err)
+
+	dstFile, err := os.OpenFile(remoteFilePath, os.O_RDWR, 0666)
+	suite.NoError(err)
+	err = dstFile.Truncate(size)
+	suite.NoError(err)
+
+	err = srcFile.Close()
+	suite.NoError(err)
+	err = dstFile.Close()
+	suite.NoError(err)
+}
+
+func (suite *dataValidationTestSuite) helperWriteToFile(localFilePath string, remoteFilePath string, offset int64, size int) {
+	buffer := make([]byte, 1*1024*1024)
+
+	localFile, err := os.OpenFile(localFilePath, os.O_RDWR, 0666)
+	suite.NoError(err)
+
+	remoteFile, err := os.OpenFile(remoteFilePath, os.O_RDWR, 0666)
+	suite.NoError(err)
+
+	n, err := localFile.WriteAt(buffer[0:size], offset)
+	suite.Equal(size, n)
+	suite.NoError(err)
+
+	n, err = remoteFile.WriteAt(buffer[0:size], offset)
+	suite.Equal(size, n)
+	suite.NoError(err)
+
+	err = localFile.Close()
+	suite.NoError(err)
+	err = remoteFile.Close()
+	suite.NoError(err)
+}
+
 //----------------Utility Functions-----------------------
 
 // pass the file name and the function returns the LocalFilePath and MountedFilePath
@@ -200,13 +278,6 @@ func writeSparseData(suite *dataValidationTestSuite, fh *os.File, offsets []int6
 	}
 }
 
-func min(x, y int) int {
-	if x < y {
-		return x
-	}
-	return y
-}
-
 // Creates the file with filePath and puts random data of size bytes
 func generateFileWithRandomData(suite *dataValidationTestSuite, filePath string, size int) {
 	fh, err := os.Create(filePath)
@@ -293,7 +364,7 @@ func (suite *dataValidationTestSuite) TestSmallFileData() {
 	// delete the cache directory
 	suite.dataValidationTestCleanup([]string{tObj.testCachePath})
 
-	suite.validateData(localFilePath, remoteFilePath)
+	suite.helperValidateFileContent(localFilePath, remoteFilePath)
 
 	suite.dataValidationTestCleanup([]string{localFilePath, remoteFilePath, tObj.testCachePath})
 }
@@ -322,7 +393,7 @@ func (suite *dataValidationTestSuite) TestMediumFileData() {
 	// delete the cache directory
 	suite.dataValidationTestCleanup([]string{tObj.testCachePath})
 
-	suite.validateData(localFilePath, remoteFilePath)
+	suite.helperValidateFileContent(localFilePath, remoteFilePath)
 
 	suite.dataValidationTestCleanup([]string{localFilePath, remoteFilePath, tObj.testCachePath})
 }
@@ -351,7 +422,7 @@ func (suite *dataValidationTestSuite) TestLargeFileData() {
 	// delete the cache directory
 	suite.dataValidationTestCleanup([]string{tObj.testCachePath})
 
-	suite.validateData(localFilePath, remoteFilePath)
+	suite.helperValidateFileContent(localFilePath, remoteFilePath)
 
 	suite.dataValidationTestCleanup([]string{localFilePath, remoteFilePath, tObj.testCachePath})
 }
@@ -426,7 +497,7 @@ func validateMultipleFilesData(jobs <-chan int, results chan<- string, fileSize
 
 		suite.copyToMountDir(localFilePath, remoteFilePath)
 		suite.dataValidationTestCleanup([]string{tObj.testCachePath + "/" + fileName})
-		suite.validateData(localFilePath, remoteFilePath)
+		suite.helperValidateFileContent(localFilePath, remoteFilePath)
 
 		suite.dataValidationTestCleanup([]string{localFilePath, tObj.testCachePath + "/" + fileName})
 
@@ -516,7 +587,7 @@ func (suite *dataValidationTestSuite) TestSparseFileRandomWrite() {
 	suite.NoError(err)
 	suite.Equal(165*int64(_1MB), fi.Size())
 
-	suite.validateData(localFilePath, remoteFilePath)
+	suite.helperValidateFileContent(localFilePath, remoteFilePath)
 
 	suite.dataValidationTestCleanup([]string{localFilePath, remoteFilePath, tObj.testCachePath})
 }
@@ -539,7 +610,7 @@ func (suite *dataValidationTestSuite) TestSparseFileRandomWriteBlockOverlap() {
 	suite.NoError(err)
 	suite.Equal(171*int64(_1MB), fi.Size())
 
-	suite.validateData(localFilePath, remoteFilePath)
+	suite.helperValidateFileContent(localFilePath, remoteFilePath)
 
 	suite.dataValidationTestCleanup([]string{localFilePath, remoteFilePath, tObj.testCachePath})
 }
@@ -594,7 +665,7 @@ func (suite *dataValidationTestSuite) TestFileReadBytesMultipleBlocks() {
 
 	closeFileHandles(suite, fh)
 
-	suite.validateData(localFilePath, remoteFilePath)
+	suite.helperValidateFileContent(localFilePath, remoteFilePath)
 
 	suite.dataValidationTestCleanup([]string{localFilePath, remoteFilePath, tObj.testCachePath})
 }
@@ -639,7 +710,7 @@ func (suite *dataValidationTestSuite) TestFileReadBytesOneBlock() {
 
 	closeFileHandles(suite, fh)
 
-	suite.validateData(localFilePath, remoteFilePath)
+	suite.helperValidateFileContent(localFilePath, remoteFilePath)
 
 	suite.dataValidationTestCleanup([]string{localFilePath, remoteFilePath, tObj.testCachePath})
 }
@@ -669,7 +740,7 @@ func (suite *dataValidationTestSuite) TestRandomWriteRaceCondition() {
 	suite.NoError(err)
 	suite.Equal(145*int64(_1MB), fi.Size())
 
-	suite.validateData(localFilePath, remoteFilePath)
+	suite.helperValidateFileContent(localFilePath, remoteFilePath)
 
 	suite.dataValidationTestCleanup([]string{localFilePath, remoteFilePath, tObj.testCachePath})
 }
diff --git a/test/e2e_tests/truncate_test.go b/test/e2e_tests/truncate_test.go
new file mode 100644
index 0000000..fa81376
--- /dev/null
+++ b/test/e2e_tests/truncate_test.go
@@ -0,0 +1,124 @@
+//go:build !unittest
+// +build !unittest
+
+/*
+    _____           _____   _____   ____          ______  _____  ------
+   |     |  |      |     | |     | |     |     | |       |            |
+   |     |  |      |     | |     | |     |     | |       |            |
+   | --- |  |      |     | |-----| |---- |     | |-----| |-----  ------
+   |     |  |      |     | |     | |     |     |       | |       |
+   | ____|  |_____ | ____| | ____| |     |_____|  _____| |_____  |_____
+
+
+   Licensed under the MIT License <http://opensource.org/licenses/MIT>.
+
+   Copyright © 2020-2025 Microsoft Corporation. All rights reserved.
+   Author : <blobfusedev@microsoft.com>
+
+   Permission is hereby granted, free of charge, to any person obtaining a copy
+   of this software and associated documentation files (the "Software"), to deal
+   in the Software without restriction, including without limitation the rights
+   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+   copies of the Software, and to permit persons to whom the Software is
+   furnished to do so, subject to the following conditions:
+
+   The above copyright notice and this permission notice shall be included in all
+   copies or substantial portions of the Software.
+
+   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+   SOFTWARE
+*/
+
+package e2e_tests
+
+import "os"
+
+func (suite *dataValidationTestSuite) TestShrinkExistingFile() {
+	fileName := "shrink_existing_file"
+	localFilePath, remoteFilePath := convertFileNameToFilePath(fileName)
+
+	t := func(initSize int64, shrinkSize int64) {
+		suite.helperCreateFile(localFilePath, remoteFilePath, initSize)
+		suite.helperValidateFileContent(localFilePath, remoteFilePath)
+		suite.helperTruncateFile(localFilePath, remoteFilePath, shrinkSize)
+		suite.helperValidateFileContent(localFilePath, remoteFilePath)
+		suite.dataValidationTestCleanup([]string{localFilePath, remoteFilePath, tObj.testCachePath})
+	}
+
+	t(1024*1024, 512*1024)
+	t(8*1024*1024, 4*1024*1024)
+	t(32*1024*1024, 16*1024*1024)
+	t(32*1024*1024+16, 16*1024*1024-9)
+	t(1*1024*1024*1024, 512*1024*1024-18)
+	t(10*1024*1024*1024, 1*1024*1024-8)
+}
+
+func (suite *dataValidationTestSuite) TestExpandExistingFile() {
+	fileName := "expand_existing_file"
+	localFilePath, remoteFilePath := convertFileNameToFilePath(fileName)
+
+	t := func(initSize int64, expandSize int64) {
+		suite.helperCreateFile(localFilePath, remoteFilePath, initSize)
+		suite.helperValidateFileContent(localFilePath, remoteFilePath)
+		suite.helperTruncateFile(localFilePath, remoteFilePath, expandSize)
+		suite.helperValidateFileContent(localFilePath, remoteFilePath)
+		suite.dataValidationTestCleanup([]string{localFilePath, remoteFilePath, tObj.testCachePath})
+	}
+
+	t(1024*1024, 2*1024*1024)
+	t(8*1024*1024, 16*1024*1024)
+	t(8*1024*1024-1, 16*1024*1024+18)
+	t(16*1024*1024-1, 256*1024*1024+18)
+	t(1*1024*1024*1024, 2*1024*1024*1024+16)
+	t(1*1024*1024, 10*1024*1024*1024+8)
+}
+
+func (suite *dataValidationTestSuite) TestTruncateNonExistingFile() {
+	fileName := "truncate_non_existing_file"
+	localFilePath, remoteFilePath := convertFileNameToFilePath(fileName)
+
+	lErr := os.Truncate(localFilePath, 1024*1024)
+	suite.Error(lErr)
+
+	rErr := os.Truncate(remoteFilePath, 1024*1024)
+	suite.Error(rErr)
+
+	suite.dataValidationTestCleanup([]string{localFilePath, remoteFilePath, tObj.testCachePath})
+}
+
+func (suite *dataValidationTestSuite) TestWriteBeforeTruncate() {
+	fileName := "write_before_truncate"
+	localFilePath, remoteFilePath := convertFileNameToFilePath(fileName)
+
+	suite.helperCreateFile(localFilePath, remoteFilePath, 1024*1024)
+	suite.helperWriteToFile(localFilePath, remoteFilePath, 512*1024, 512*1024)
+	suite.helperTruncateFile(localFilePath, remoteFilePath, 512*1024)
+	suite.helperValidateFileContent(localFilePath, remoteFilePath)
+	suite.dataValidationTestCleanup([]string{localFilePath, remoteFilePath, tObj.testCachePath})
+}
+
+func (suite *dataValidationTestSuite) TestWriteAfterTruncate() {
+	fileName := "write_after_truncate"
+	localFilePath, remoteFilePath := convertFileNameToFilePath(fileName)
+
+	suite.helperCreateFile(localFilePath, remoteFilePath, 0)
+	suite.helperTruncateFile(localFilePath, remoteFilePath, 512*1024)
+	suite.helperWriteToFile(localFilePath, remoteFilePath, 512*1024, 512*1024)
+	suite.helperValidateFileContent(localFilePath, remoteFilePath)
+	suite.dataValidationTestCleanup([]string{localFilePath, remoteFilePath, tObj.testCachePath})
+}
+
+func (suite *dataValidationTestSuite) TestTruncateToZero() {
+	fileName := "truncate_to_zero"
+	localFilePath, remoteFilePath := convertFileNameToFilePath(fileName)
+
+	suite.helperCreateFile(localFilePath, remoteFilePath, 1024*1024)
+	suite.helperTruncateFile(localFilePath, remoteFilePath, 0)
+	suite.helperValidateFileContent(localFilePath, remoteFilePath)
+	suite.dataValidationTestCleanup([]string{localFilePath, remoteFilePath, tObj.testCachePath})
+}
diff --git a/testdata/config/azure_key_directio.yaml b/testdata/config/azure_key_directio.yaml
index dded1ce..57e2571 100644
--- a/testdata/config/azure_key_directio.yaml
+++ b/testdata/config/azure_key_directio.yaml
@@ -18,7 +18,7 @@ libfuse:
 file_cache:
   path: { 1 }
   timeout-sec: 0
-  max-size-mb: 2048
+  max-size-mb: 20480
   allow-non-empty-temp: true
   cleanup-on-start: true