btrfs: allow setting NOCOW for a zero sized file via ioctl
David Sterba [Fri, 7 Sep 2012 11:56:55 +0000 (05:56 -0600)]
Hi,

the patch si simple, but it has user visible impact and I'm not quite sure how
to resolve it.

In short, $subj says it, chattr -C supports it and we want to use it.

The conditions that acutally allow to change the NOCOW flag are clear. What if
I try to set the flag on a file that is not empty? Options:

1) whole ioctl will fail, EINVAL
2.1) ioctl will succeed, the NOCOW flag will be silently removed, but the file
     will stay COW-ed and checksummed
2.2) ioctl will succeed, flag will not be removed and a syslog message will
     warn that the COW flag has not been changed
2.2.1) dtto, no syslog message

Man page of chattr states that

 "If it is set on a file which already has data blocks, it is undefined when
 the blocks assigned to the file will be fully stable."

Yes, it's undefined and with current implementation it'll never happen. So from
this end, the user cannot expect anything. I'm trying to find a reasonable
behaviour, so that a command like 'chattr -R -aijS +C' to tweak a broad set of
flags in a deep directory does not fail unnecessarily and does not pollute the
log.

My personal preference is 2.2.1, but my dev's oppinion is skewed, not counting
the fact that I know the code and otherwise would look there before consulting
the documentation.

The patch implements 2.2.1.

david

-------------8<-------------------
From: David Sterba <dsterba@suse.cz>

It's safe to turn off checksums for a zero sized file.

http://thread.gmane.org/gmane.comp.file-systems.btrfs/18030

"We cannot switch on NODATASUM for a file that already has extents that
are checksummed. The invariant here is that either all the extents or
none are checksummed.

Theoretically it's possible to add/remove all checksums from a given
file, but it's a potentially longtime operation, the file has to be in
some intermediate state where the checksums partially exist but have to
be ignored (for the csum->nocsum) until the file is fully converted,
this brings more special cases to extent handling, it has to survive
power failure and remain consistent, and probably needs to be restarted
after next mount."

Signed-off-by: David Sterba <dsterba@suse.cz>

fs/btrfs/ioctl.c

index 38e3d6a..4d7f4bb 100644 (file)
@@ -181,6 +181,7 @@ static int btrfs_ioctl_setflags(struct file *file, void __user *arg)
        int ret;
        u64 ip_oldflags;
        unsigned int i_oldflags;
+       umode_t mode;
 
        if (btrfs_root_readonly(root))
                return -EROFS;
@@ -203,6 +204,7 @@ static int btrfs_ioctl_setflags(struct file *file, void __user *arg)
 
        ip_oldflags = ip->flags;
        i_oldflags = inode->i_flags;
+       mode = inode->i_mode;
 
        flags = btrfs_mask_flags(inode->i_mode, flags);
        oldflags = btrfs_flags_to_ioctl(ip->flags);
@@ -237,10 +239,31 @@ static int btrfs_ioctl_setflags(struct file *file, void __user *arg)
                ip->flags |= BTRFS_INODE_DIRSYNC;
        else
                ip->flags &= ~BTRFS_INODE_DIRSYNC;
-       if (flags & FS_NOCOW_FL)
-               ip->flags |= BTRFS_INODE_NODATACOW;
-       else
-               ip->flags &= ~BTRFS_INODE_NODATACOW;
+       if (flags & FS_NOCOW_FL) {
+               if (S_ISREG(mode)) {
+                       /*
+                        * It's safe to turn csums off here, no extents exist.
+                        * Otherwise we want the flag to reflect the real COW
+                        * status of the file and will not set it.
+                        */
+                       if (inode->i_size == 0)
+                               ip->flags |= BTRFS_INODE_NODATACOW
+                                          | BTRFS_INODE_NODATASUM;
+               } else {
+                       ip->flags |= BTRFS_INODE_NODATACOW;
+               }
+       } else {
+               /*
+                * Revert back under same assuptions as above
+                */
+               if (S_ISREG(mode)) {
+                       if (inode->i_size == 0)
+                               ip->flags &= ~(BTRFS_INODE_NODATACOW
+                                            | BTRFS_INODE_NODATASUM);
+               } else {
+                       ip->flags &= ~BTRFS_INODE_NODATACOW;
+               }
+       }
 
        /*
         * The COMPRESS flag can only be changed by users, while the NOCOMPRESS