@@ -57,7 +57,10 @@ static void enforce_ownership(int dirfd, const char *name, int fd = -1) {
5757 }
5858
5959 if (res == -1 ) {
60- error_print (" WARNING: Failed to chown created file '{}': {}" , name, errno);
60+ if (name)
61+ error_print (" WARNING: Failed to chown created file '{}': {}" , name, errno);
62+ else
63+ error_print (" WARNING: Failed to fchown fd {}: {}" , fd, errno);
6164 }
6265}
6366
@@ -74,8 +77,9 @@ static void forget_one(fuse_ino_t ino, uint64_t n) {
7477 debug_print (" forget: inode {} count now {}" , inode.src_ino , inode.nlookup .load ());
7578
7679 if (!inode.nlookup ) {
77- std::lock_guard<std::mutex> g_fs{fs.mutex };
7880 l.unlock ();
81+ std::lock_guard<std::mutex> g_fs{fs.mutex };
82+
7983 if (!inode.nlookup ) {
8084 debug_print (" forget: cleaning up inode {}" , inode.src_ino );
8185 fs.inodes .erase ({inode.src_ino , inode.src_dev });
@@ -192,6 +196,26 @@ static void sfs_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, int v
192196 int ifd = inode.fd ;
193197 int res = 0 ;
194198
199+ if (valid & FUSE_SET_ATTR_MODE ) {
200+ res = with_fd_path (ifd,
201+ [attr](const char *procname) { return chmod (procname, attr->st_mode ); });
202+ if (res == -1 ) {
203+ fuse_reply_err (req, errno);
204+ return ;
205+ }
206+ }
207+
208+ if (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID )) {
209+ uid_t u = (valid & FUSE_SET_ATTR_UID ) ? attr->st_uid : static_cast <uid_t >(-1 );
210+ gid_t g = (valid & FUSE_SET_ATTR_GID ) ? attr->st_gid : static_cast <gid_t >(-1 );
211+
212+ res = with_fd_path (ifd, [u, g](const char *procname) { return lchown (procname, u, g); });
213+ if (res == -1 ) {
214+ fuse_reply_err (req, errno);
215+ return ;
216+ }
217+ }
218+
195219 if (valid & FUSE_SET_ATTR_SIZE ) {
196220 std::unique_lock<std::mutex> g{inode.m };
197221
@@ -228,6 +252,43 @@ static void sfs_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, int v
228252 g.unlock ();
229253 }
230254
255+ if (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME )) {
256+ struct timespec tv[2 ];
257+
258+ tv[0 ].tv_sec = 0 ;
259+ tv[0 ].tv_nsec = UTIME_OMIT ;
260+ tv[1 ].tv_sec = 0 ;
261+ tv[1 ].tv_nsec = UTIME_OMIT ;
262+
263+ if (valid & FUSE_SET_ATTR_ATIME ) {
264+ if (valid & FUSE_SET_ATTR_ATIME_NOW ) {
265+ tv[0 ].tv_nsec = UTIME_NOW ;
266+ } else {
267+ tv[0 ] = attr->st_atim ;
268+ }
269+ }
270+
271+ if (valid & FUSE_SET_ATTR_MTIME ) {
272+ if (valid & FUSE_SET_ATTR_MTIME_NOW ) {
273+ tv[1 ].tv_nsec = UTIME_NOW ;
274+ } else {
275+ tv[1 ] = attr->st_mtim ;
276+ }
277+ }
278+
279+ if (fi) {
280+ res = futimens (fi->fh , tv);
281+ } else {
282+ std::string procname = std::format (" /proc/self/fd/{}" , ifd);
283+ res = utimensat (AT_FDCWD , procname.c_str (), tv, 0 );
284+ }
285+
286+ if (res == -1 ) {
287+ fuse_reply_err (req, errno);
288+ return ;
289+ }
290+ }
291+
231292 return sfs_getattr (req, ino, fi);
232293}
233294
@@ -250,9 +311,29 @@ static void sfs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) {
250311
251312static void mknod_symlink (fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode,
252313 dev_t rdev, const char *link) {
253- int res;
254314 Inode &inode_p = get_inode (parent);
255315
316+ if (fs.quota .is_enabled ()) {
317+ if (S_ISDIR (mode)) {
318+ if (!fs.quota .reserve (0 , fs.blocksize )) {
319+ fuse_reply_err (req, ENOSPC );
320+ return ;
321+ }
322+ } else if (S_ISLNK (mode) && link) {
323+ uint64_t link_size = strlen (link);
324+ if (!fs.quota .reserve (0 , link_size)) {
325+ fuse_reply_err (req, ENOSPC );
326+ return ;
327+ }
328+ } else if (S_ISREG (mode)) {
329+ if (!fs.quota .check_available (0 )) {
330+ fuse_reply_err (req, ENOSPC );
331+ return ;
332+ }
333+ }
334+ }
335+
336+ int res;
256337 if (S_ISDIR (mode))
257338 res = mkdirat (inode_p.fd , name, mode);
258339 else if (S_ISLNK (mode))
@@ -262,6 +343,14 @@ static void mknod_symlink(fuse_req_t req, fuse_ino_t parent, const char *name, m
262343
263344 int saverr = errno;
264345 if (res == -1 ) {
346+ if (fs.quota .is_enabled ()) {
347+ if (S_ISDIR (mode)) {
348+ fs.quota .release (fs.blocksize , 0 );
349+ } else if (S_ISLNK (mode) && link) {
350+ fs.quota .release (strlen (link), 0 );
351+ }
352+ }
353+
265354 if (saverr == ENFILE || saverr == EMFILE )
266355 error_print (" Reached maximum number of file descriptors." );
267356 fuse_reply_err (req, saverr);
@@ -319,9 +408,24 @@ static void sfs_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t parent, const ch
319408
320409static void sfs_rmdir (fuse_req_t req, fuse_ino_t parent, const char *name) {
321410 Inode &inode_p = get_inode (parent);
411+
412+ uint64_t dir_size = 0 ;
413+ if (fs.quota .is_enabled ()) {
414+ struct stat st;
415+ if (fstatat (inode_p.fd , name, &st, AT_SYMLINK_NOFOLLOW ) == 0 ) {
416+ dir_size = st.st_size ;
417+ }
418+ }
419+
322420 std::lock_guard<std::mutex> g{inode_p.m };
323421 auto res = unlinkat (inode_p.fd , name, AT_REMOVEDIR );
324- fuse_reply_err (req, res == -1 ? errno : 0 );
422+ if (res == -1 ) {
423+ fuse_reply_err (req, errno);
424+ } else {
425+ if (dir_size > 0 )
426+ fs.quota .release (dir_size, 0 );
427+ fuse_reply_err (req, 0 );
428+ }
325429}
326430
327431static void sfs_rename (fuse_req_t req, fuse_ino_t parent, const char *name, fuse_ino_t newparent,
@@ -332,8 +436,25 @@ static void sfs_rename(fuse_req_t req, fuse_ino_t parent, const char *name, fuse
332436 fuse_reply_err (req, EINVAL );
333437 return ;
334438 }
439+
440+ uint64_t victim_size = 0 ;
441+ nlink_t victim_nlink = 0 ;
442+ if (fs.quota .is_enabled ()) {
443+ struct stat st;
444+ if (fstatat (inode_np.fd , newname, &st, AT_SYMLINK_NOFOLLOW ) == 0 ) {
445+ victim_size = st.st_size ;
446+ victim_nlink = st.st_nlink ;
447+ }
448+ }
449+
335450 auto res = renameat (inode_p.fd , name, inode_np.fd , newname);
336- fuse_reply_err (req, res == -1 ? errno : 0 );
451+ if (res == -1 ) {
452+ fuse_reply_err (req, errno);
453+ } else {
454+ if (victim_size > 0 && victim_nlink == 1 )
455+ fs.quota .release (victim_size, 0 );
456+ fuse_reply_err (req, 0 );
457+ }
337458}
338459
339460static void sfs_unlink (fuse_req_t req, fuse_ino_t parent, const char *name) {
@@ -346,9 +467,22 @@ static void sfs_unlink(fuse_req_t req, fuse_ino_t parent, const char *name) {
346467 return ;
347468 }
348469
470+ auto res = unlinkat (inode_p.fd , name, 0 );
471+ if (res == -1 ) {
472+ forget_one (e.ino , 1 );
473+ fuse_reply_err (req, errno);
474+ return ;
475+ }
476+
349477 if (fs.quota .is_enabled () && e.attr .st_nlink == 1 ) {
350- fs.quota .release (e.attr .st_size , 0 );
351- debug_print (" QUOTA: Reclaimed {} bytes from unlink" , e.attr .st_size );
478+ Inode &inode = get_inode (e.ino );
479+ std::lock_guard<std::mutex> g{inode.m };
480+ uint64_t size = inode.known_size ;
481+ if (S_ISLNK (e.attr .st_mode )) {
482+ size = e.attr .st_size ;
483+ }
484+ fs.quota .release (size, 0 );
485+ debug_print (" QUOTA: Reclaimed {} bytes from unlink" , size);
352486 }
353487
354488 if (!fs.timeout ) {
@@ -366,8 +500,7 @@ static void sfs_unlink(fuse_req_t req, fuse_ino_t parent, const char *name) {
366500
367501 forget_one (e.ino , 1 );
368502
369- auto res = unlinkat (inode_p.fd , name, 0 );
370- fuse_reply_err (req, res == -1 ? errno : 0 );
503+ fuse_reply_err (req, 0 );
371504}
372505
373506static void sfs_forget (fuse_req_t req, fuse_ino_t ino, uint64_t nlookup) {
@@ -561,18 +694,42 @@ static void sfs_create(fuse_req_t req, fuse_ino_t parent, const char *name, mode
561694 debug_print (" sfs_create: parent ino {}, name '{}', mode {:o}, flags {:o}" , parent, name, mode,
562695 fi->flags );
563696
697+ uint64_t old_file_size = 0 ;
698+ bool truncating_existing = false ;
699+
700+ if (fs.quota .is_enabled ()) {
701+ if (fi->flags & O_TRUNC ) {
702+ struct stat st;
703+ if (fstatat (inode_p.fd , name, &st, AT_SYMLINK_NOFOLLOW ) == 0 && S_ISREG (st.st_mode )) {
704+ old_file_size = st.st_size ;
705+ truncating_existing = true ;
706+ }
707+ }
708+
709+ if (!truncating_existing && !fs.quota .check_available (0 )) {
710+ fuse_reply_err (req, ENOSPC );
711+ return ;
712+ }
713+ }
714+
564715 int fd = openat (inode_p.fd , name, (fi->flags | O_CREAT ) & ~O_NOFOLLOW , mode);
565716 if (fd == -1 ) {
566717 fuse_reply_err (req, errno);
567718 return ;
568719 }
569720
721+ if (truncating_existing && old_file_size > 0 ) {
722+ fs.quota .release (old_file_size, 0 );
723+ debug_print (" QUOTA: Released {} bytes from O_TRUNC in create" , old_file_size);
724+ }
725+
570726 enforce_ownership (inode_p.fd , name, fd);
571727
572728 fi->fh = fd;
573729 fuse_entry_param e;
574730 auto err = do_lookup (parent, name, &e);
575731 if (err) {
732+ close (fd);
576733 fuse_reply_err (req, err);
577734 return ;
578735 }
@@ -581,6 +738,10 @@ static void sfs_create(fuse_req_t req, fuse_ino_t parent, const char *name, mode
581738 std::lock_guard<std::mutex> g{inode.m };
582739 inode.nopen ++;
583740
741+ if (truncating_existing) {
742+ inode.known_size = 0 ;
743+ }
744+
584745 sfs_create_open_flags (fi);
585746
586747 fuse_reply_create (req, &e, fi);
@@ -589,6 +750,12 @@ static void sfs_create(fuse_req_t req, fuse_ino_t parent, const char *name, mode
589750static void sfs_open (fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) {
590751 Inode &inode = get_inode (ino);
591752
753+ uint64_t trunc_release = 0 ;
754+ if (fs.quota .is_enabled () && (fi->flags & O_TRUNC )) {
755+ std::lock_guard<std::mutex> g{inode.m };
756+ trunc_release = inode.known_size ;
757+ }
758+
592759 if (fs.timeout && (fi->flags & O_ACCMODE ) == O_WRONLY ) {
593760 fi->flags &= ~O_ACCMODE ;
594761 fi->flags |= O_RDWR ;
@@ -604,10 +771,19 @@ static void sfs_open(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) {
604771 return ;
605772 }
606773
774+ if (trunc_release > 0 ) {
775+ fs.quota .release (trunc_release, 0 );
776+ debug_print (" QUOTA: Released {} bytes from O_TRUNC in open" , trunc_release);
777+ }
778+
607779 enforce_ownership (-1 , nullptr , fd);
608780
609781 std::lock_guard<std::mutex> g{inode.m };
610782 inode.nopen ++;
783+
784+ if (trunc_release > 0 )
785+ inode.known_size = 0 ;
786+
611787 sfs_create_open_flags (fi);
612788 fi->fh = fd;
613789
@@ -702,10 +878,26 @@ static void sfs_write_buf(fuse_req_t req, fuse_ino_t ino, fuse_bufvec *in_buf, o
702878
703879 if (res < 0 ) {
704880 if (reserved) {
881+ std::lock_guard<std::mutex> g{inode.m };
882+ inode.known_size = pre_write_size;
705883 fs.quota .release (pre_write_size + amount_reserved, pre_write_size);
706884 }
707885 fuse_reply_err (req, -res);
708886 } else {
887+ if (reserved) {
888+ uint64_t actual_end = off + static_cast <uint64_t >(res);
889+ if (actual_end < write_end) {
890+ std::lock_guard<std::mutex> g{inode.m };
891+
892+ if (inode.known_size == write_end) {
893+ uint64_t corrected = std::max (pre_write_size, actual_end);
894+ if (corrected < write_end) {
895+ fs.quota .release (write_end, corrected);
896+ inode.known_size = corrected;
897+ }
898+ }
899+ }
900+ }
709901 fuse_reply_write (req, (size_t )res);
710902 }
711903}
@@ -760,8 +952,12 @@ static void sfs_fallocate(fuse_req_t req, fuse_ino_t ino, int mode, off_t offset
760952 err = errno;
761953
762954 if (err != 0 ) {
763- if (reserved)
955+ if (reserved) {
956+ std::lock_guard<std::mutex> g2{inode.m };
957+ if (inode.known_size == end)
958+ inode.known_size = current;
764959 fs.quota .release (end, current);
960+ }
765961 fuse_reply_err (req, err);
766962 } else {
767963 fuse_reply_err (req, 0 );
@@ -783,8 +979,6 @@ static void sfs_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name, size_
783979 ssize_t ret = getxattr (procname.c_str (), name, value.data (), size);
784980 if (ret == -1 )
785981 fuse_reply_err (req, errno);
786- else if (ret == 0 )
787- fuse_reply_err (req, 0 );
788982 else
789983 fuse_reply_buf (req, value.data (), ret);
790984 } else {
@@ -805,8 +999,6 @@ static void sfs_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size) {
805999 ssize_t ret = listxattr (procname.c_str (), value.data (), size);
8061000 if (ret == -1 )
8071001 fuse_reply_err (req, errno);
808- else if (ret == 0 )
809- fuse_reply_err (req, 0 );
8101002 else
8111003 fuse_reply_buf (req, value.data (), ret);
8121004 } else {
0 commit comments