@@ -9,7 +9,7 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH};
99
1010use fuser:: {
1111 BackingId , FileType , Filesystem , ReplyAttr , ReplyData , ReplyDirectory , ReplyEmpty , ReplyEntry ,
12- ReplyIoctl , ReplyOpen , ReplyWrite , Request , TimeOrNow ,
12+ ReplyIoctl , ReplyOpen , ReplyStatfs , ReplyWrite , Request , TimeOrNow ,
1313} ;
1414use parking_lot:: RwLock ;
1515
@@ -714,12 +714,27 @@ impl Filesystem for BranchFs {
714714 // to the same inode (after COW is already done).
715715 if let Some ( file) = self . write_cache . get ( ino, epoch) {
716716 use std:: io:: { Seek , SeekFrom , Write } ;
717+ // Check quota for writes that extend the file
718+ let old_size = file. metadata ( ) . map ( |m| m. len ( ) ) . unwrap_or ( 0 ) ;
719+ let write_end = offset as u64 + data. len ( ) as u64 ;
720+ if write_end > old_size {
721+ if self . manager . quota . check ( write_end - old_size) . is_err ( ) {
722+ reply. error ( libc:: ENOSPC ) ;
723+ return ;
724+ }
725+ }
717726 if file. seek ( SeekFrom :: Start ( offset as u64 ) ) . is_err ( ) {
718727 reply. error ( libc:: EIO ) ;
719728 return ;
720729 }
721730 match file. write ( data) {
722- Ok ( n) => reply. written ( n as u32 ) ,
731+ Ok ( n) => {
732+ let new_end = offset as u64 + n as u64 ;
733+ if new_end > old_size {
734+ self . manager . quota . add ( new_end - old_size) ;
735+ }
736+ reply. written ( n as u32 )
737+ }
723738 Err ( _) => reply. error ( libc:: EIO ) ,
724739 }
725740 return ;
@@ -780,6 +795,14 @@ impl Filesystem for BranchFs {
780795 // Serve from the just-cached write fd
781796 if let Some ( file) = self . write_cache . get ( ino, epoch) {
782797 use std:: io:: { Seek , SeekFrom , Write } ;
798+ let old_size = file. metadata ( ) . map ( |m| m. len ( ) ) . unwrap_or ( 0 ) ;
799+ let write_end = offset as u64 + data. len ( ) as u64 ;
800+ if write_end > old_size {
801+ if self . manager . quota . check ( write_end - old_size) . is_err ( ) {
802+ reply. error ( libc:: ENOSPC ) ;
803+ return ;
804+ }
805+ }
783806 if file. seek ( SeekFrom :: Start ( offset as u64 ) ) . is_err ( ) {
784807 reply. error ( libc:: EIO ) ;
785808 return ;
@@ -790,6 +813,10 @@ impl Filesystem for BranchFs {
790813 reply. error ( libc:: ESTALE ) ;
791814 return ;
792815 }
816+ let new_end = offset as u64 + n as u64 ;
817+ if new_end > old_size {
818+ self . manager . quota . add ( new_end - old_size) ;
819+ }
793820 reply. written ( n as u32 )
794821 }
795822 Err ( _) => reply. error ( libc:: EIO ) ,
@@ -1076,7 +1103,9 @@ impl Filesystem for BranchFs {
10761103 b. add_tombstone ( & rel_path) ?;
10771104 let delta = b. delta_path ( & rel_path) ;
10781105 if delta. exists ( ) {
1106+ let freed = delta. symlink_metadata ( ) . map ( |m| m. len ( ) ) . unwrap_or ( 0 ) ;
10791107 std:: fs:: remove_file ( & delta) ?;
1108+ self . manager . quota . sub ( freed) ;
10801109 }
10811110 Ok ( ( ) )
10821111 } ) ;
@@ -1106,7 +1135,9 @@ impl Filesystem for BranchFs {
11061135 b. add_tombstone ( & path) ?;
11071136 let delta = b. delta_path ( & path) ;
11081137 if delta. exists ( ) {
1138+ let freed = delta. symlink_metadata ( ) . map ( |m| m. len ( ) ) . unwrap_or ( 0 ) ;
11091139 std:: fs:: remove_file ( & delta) ?;
1140+ self . manager . quota . sub ( freed) ;
11101141 }
11111142 Ok ( ( ) )
11121143 } ) ;
@@ -1450,11 +1481,30 @@ impl Filesystem for BranchFs {
14501481 return ;
14511482 }
14521483 if let Some ( new_size) = size {
1453- if let Ok ( delta) = self . ensure_cow_for_branch ( & branch, & rel_path) {
1454- let file = std:: fs:: OpenOptions :: new ( ) . write ( true ) . open ( & delta) ;
1455- if let Ok ( f) = file {
1456- let _ = f. set_len ( new_size) ;
1484+ match self . ensure_cow_for_branch ( & branch, & rel_path) {
1485+ Ok ( delta) => {
1486+ let file = std:: fs:: OpenOptions :: new ( ) . write ( true ) . open ( & delta) ;
1487+ if let Ok ( f) = file {
1488+ let old_size = f. metadata ( ) . map ( |m| m. len ( ) ) . unwrap_or ( 0 ) ;
1489+ if new_size > old_size {
1490+ if self . manager . quota . check ( new_size - old_size) . is_err ( ) {
1491+ reply. error ( libc:: ENOSPC ) ;
1492+ return ;
1493+ }
1494+ }
1495+ let _ = f. set_len ( new_size) ;
1496+ if new_size > old_size {
1497+ self . manager . quota . add ( new_size - old_size) ;
1498+ } else if old_size > new_size {
1499+ self . manager . quota . sub ( old_size - new_size) ;
1500+ }
1501+ }
1502+ }
1503+ Err ( e) if e. raw_os_error ( ) == Some ( libc:: ENOSPC ) => {
1504+ reply. error ( libc:: ENOSPC ) ;
1505+ return ;
14571506 }
1507+ Err ( _) => { }
14581508 }
14591509 }
14601510 if mode. is_some ( )
@@ -1478,11 +1528,30 @@ impl Filesystem for BranchFs {
14781528 _ => {
14791529 // Root path (existing logic)
14801530 if let Some ( new_size) = size {
1481- if let Ok ( delta) = self . ensure_cow ( & path) {
1482- let file = std:: fs:: OpenOptions :: new ( ) . write ( true ) . open ( & delta) ;
1483- if let Ok ( f) = file {
1484- let _ = f. set_len ( new_size) ;
1531+ match self . ensure_cow ( & path) {
1532+ Ok ( delta) => {
1533+ let file = std:: fs:: OpenOptions :: new ( ) . write ( true ) . open ( & delta) ;
1534+ if let Ok ( f) = file {
1535+ let old_size = f. metadata ( ) . map ( |m| m. len ( ) ) . unwrap_or ( 0 ) ;
1536+ if new_size > old_size {
1537+ if self . manager . quota . check ( new_size - old_size) . is_err ( ) {
1538+ reply. error ( libc:: ENOSPC ) ;
1539+ return ;
1540+ }
1541+ }
1542+ let _ = f. set_len ( new_size) ;
1543+ if new_size > old_size {
1544+ self . manager . quota . add ( new_size - old_size) ;
1545+ } else if old_size > new_size {
1546+ self . manager . quota . sub ( old_size - new_size) ;
1547+ }
1548+ }
1549+ }
1550+ Err ( e) if e. raw_os_error ( ) == Some ( libc:: ENOSPC ) => {
1551+ reply. error ( libc:: ENOSPC ) ;
1552+ return ;
14851553 }
1554+ Err ( _) => { }
14861555 }
14871556 }
14881557 if mode. is_some ( )
@@ -1817,4 +1886,47 @@ impl Filesystem for BranchFs {
18171886 }
18181887 }
18191888 }
1889+
1890+ fn statfs ( & mut self , _req : & Request , _ino : u64 , reply : ReplyStatfs ) {
1891+ let block_size = 4096u32 ;
1892+ let quota = & self . manager . quota ;
1893+
1894+ if let Some ( max_bytes) = quota. max ( ) {
1895+ let used = quota. used ( ) ;
1896+ let total_blocks = max_bytes / block_size as u64 ;
1897+ let used_blocks = used. min ( max_bytes) / block_size as u64 ;
1898+ let avail_blocks = total_blocks. saturating_sub ( used_blocks) ;
1899+
1900+ reply. statfs (
1901+ total_blocks, // total blocks
1902+ avail_blocks, // free blocks
1903+ avail_blocks, // available blocks (to unprivileged users)
1904+ 0 , // total inodes (0 = unspecified)
1905+ 0 , // free inodes
1906+ block_size, // block size
1907+ 255 , // max name length
1908+ block_size, // fragment size
1909+ ) ;
1910+ } else {
1911+ // No quota — report from the storage filesystem
1912+ let storage_path = & self . manager . storage_path ;
1913+ match nix:: sys:: statvfs:: statvfs ( storage_path) {
1914+ Ok ( stat) => {
1915+ reply. statfs (
1916+ stat. blocks ( ) ,
1917+ stat. blocks_free ( ) ,
1918+ stat. blocks_available ( ) ,
1919+ stat. files ( ) ,
1920+ stat. files_free ( ) ,
1921+ stat. block_size ( ) as u32 ,
1922+ stat. name_max ( ) as u32 ,
1923+ stat. fragment_size ( ) as u32 ,
1924+ ) ;
1925+ }
1926+ Err ( _) => {
1927+ reply. error ( libc:: EIO ) ;
1928+ }
1929+ }
1930+ }
1931+ }
18201932}
0 commit comments