[XFS] Ondisk format extension for extended attributes (attr2). Basically,
[linux-block.git] / fs / xfs / xfs_attr_leaf.c
index e13eaa521436b0272120b9e45720c84c9dc1d601..50598b121683e09e5321768dd0152d48520fe4ef 100644 (file)
@@ -118,13 +118,82 @@ STATIC int xfs_attr_put_listent(xfs_attr_list_context_t *context,
 
 
 /*========================================================================
- * External routines when dirsize < XFS_LITINO(mp).
+ * External routines when attribute fork size < XFS_LITINO(mp).
  *========================================================================*/
 
 /*
- * Create the initial contents of a shortform attribute list.
+ * Query whether the requested number of additional bytes of extended
+ * attribute space will be able to fit inline.
+ * Returns zero if not, else the di_forkoff fork offset to be used in the
+ * literal area for attribute data once the new bytes have been added.
+ *
+ * di_forkoff must be 8 byte aligned, hence is stored as a >>3 value;
+ * special case for dev/uuid inodes, they have fixed size data forks.
  */
 int
+xfs_attr_shortform_bytesfit(xfs_inode_t *dp, int bytes)
+{
+       int offset;
+       int minforkoff; /* lower limit on valid forkoff locations */
+       int maxforkoff; /* upper limit on valid forkoff locations */
+       xfs_mount_t *mp = dp->i_mount;
+
+       if (unlikely(mp->m_flags & XFS_MOUNT_COMPAT_ATTR)) {
+               if (bytes <= XFS_IFORK_ASIZE(dp))
+                       return mp->m_attroffset >> 3;
+               return 0;
+       }
+
+       offset = (XFS_LITINO(mp) - bytes) >> 3; /* rounded down */
+
+       switch (dp->i_d.di_format) {
+       case XFS_DINODE_FMT_DEV:
+               minforkoff = roundup(sizeof(xfs_dev_t), 8) >> 3;
+               return (offset >= minforkoff) ? minforkoff : 0;
+       case XFS_DINODE_FMT_UUID:
+               minforkoff = roundup(sizeof(uuid_t), 8) >> 3;
+               return (offset >= minforkoff) ? minforkoff : 0;
+       }
+
+       /* data fork btree root can have at least this many key/ptr pairs */
+       minforkoff = MAX(dp->i_df.if_bytes, XFS_BMDR_SPACE_CALC(MINDBTPTRS));
+       minforkoff = roundup(minforkoff, 8) >> 3;
+
+       /* attr fork btree root can have at least this many key/ptr pairs */
+       maxforkoff = XFS_LITINO(mp) - XFS_BMDR_SPACE_CALC(MINABTPTRS);
+       maxforkoff = maxforkoff >> 3;   /* rounded down */
+
+       if (offset >= minforkoff && offset < maxforkoff)
+               return offset;
+       if (offset >= maxforkoff)
+               return maxforkoff;
+       return 0;
+}
+
+/*
+ * Switch on the ATTR2 superblock bit (implies also FEATURES2)
+ */
+STATIC void
+xfs_sbversion_add_attr2(xfs_mount_t *mp, xfs_trans_t *tp)
+{
+       unsigned long s;
+
+       if (!(mp->m_flags & XFS_MOUNT_COMPAT_ATTR) &&
+           !(XFS_SB_VERSION_HASATTR2(&mp->m_sb))) {
+               s = XFS_SB_LOCK(mp);
+               if (!XFS_SB_VERSION_HASATTR2(&mp->m_sb)) {
+                       XFS_SB_VERSION_ADDATTR2(&mp->m_sb);
+                       XFS_SB_UNLOCK(mp, s);
+                       xfs_mod_sb(tp, XFS_SB_VERSIONNUM | XFS_SB_FEATURES2);
+               } else
+                       XFS_SB_UNLOCK(mp, s);
+       }
+}
+
+/*
+ * Create the initial contents of a shortform attribute list.
+ */
+void
 xfs_attr_shortform_create(xfs_da_args_t *args)
 {
        xfs_attr_sf_hdr_t *hdr;
@@ -148,29 +217,37 @@ xfs_attr_shortform_create(xfs_da_args_t *args)
        hdr->count = 0;
        INT_SET(hdr->totsize, ARCH_CONVERT, sizeof(*hdr));
        xfs_trans_log_inode(args->trans, dp, XFS_ILOG_CORE | XFS_ILOG_ADATA);
-       return(0);
 }
 
 /*
  * Add a name/value pair to the shortform attribute list.
  * Overflow from the inode has already been checked for.
  */
-int
-xfs_attr_shortform_add(xfs_da_args_t *args)
+void
+xfs_attr_shortform_add(xfs_da_args_t *args, int forkoff)
 {
        xfs_attr_shortform_t *sf;
        xfs_attr_sf_entry_t *sfe;
        int i, offset, size;
+       xfs_mount_t *mp;
        xfs_inode_t *dp;
        xfs_ifork_t *ifp;
 
        dp = args->dp;
+       mp = dp->i_mount;
+       dp->i_d.di_forkoff = forkoff;
+       dp->i_df.if_ext_max =
+               XFS_IFORK_DSIZE(dp) / (uint)sizeof(xfs_bmbt_rec_t);
+       dp->i_afp->if_ext_max =
+               XFS_IFORK_ASIZE(dp) / (uint)sizeof(xfs_bmbt_rec_t);
+
        ifp = dp->i_afp;
        ASSERT(ifp->if_flags & XFS_IFINLINE);
        sf = (xfs_attr_shortform_t *)ifp->if_u1.if_data;
        sfe = &sf->list[0];
        for (i = 0; i < INT_GET(sf->hdr.count, ARCH_CONVERT);
                                sfe = XFS_ATTR_SF_NEXTENTRY(sfe), i++) {
+#ifdef DEBUG
                if (sfe->namelen != args->namelen)
                        continue;
                if (memcmp(args->name, sfe->nameval, args->namelen) != 0)
@@ -181,7 +258,8 @@ xfs_attr_shortform_add(xfs_da_args_t *args)
                if (((args->flags & ATTR_ROOT) != 0) !=
                    ((sfe->flags & XFS_ATTR_ROOT) != 0))
                        continue;
-               return(XFS_ERROR(EEXIST));
+               ASSERT(0);
+#endif
        }
 
        offset = (char *)sfe - (char *)sf;
@@ -200,11 +278,11 @@ xfs_attr_shortform_add(xfs_da_args_t *args)
        INT_MOD(sf->hdr.totsize, ARCH_CONVERT, size);
        xfs_trans_log_inode(args->trans, dp, XFS_ILOG_CORE | XFS_ILOG_ADATA);
 
-       return(0);
+       xfs_sbversion_add_attr2(mp, args->trans);
 }
 
 /*
- * Remove a name from the shortform attribute list structure.
+ * Remove an attribute from the shortform attribute list structure.
  */
 int
 xfs_attr_shortform_remove(xfs_da_args_t *args)
@@ -212,17 +290,16 @@ xfs_attr_shortform_remove(xfs_da_args_t *args)
        xfs_attr_shortform_t *sf;
        xfs_attr_sf_entry_t *sfe;
        int base, size=0, end, totsize, i;
+       xfs_mount_t *mp;
        xfs_inode_t *dp;
 
-       /*
-        * Remove the attribute.
-        */
        dp = args->dp;
+       mp = dp->i_mount;
        base = sizeof(xfs_attr_sf_hdr_t);
        sf = (xfs_attr_shortform_t *)dp->i_afp->if_u1.if_data;
        sfe = &sf->list[0];
-       for (i = 0; i < INT_GET(sf->hdr.count, ARCH_CONVERT);
-                               sfe = XFS_ATTR_SF_NEXTENTRY(sfe),
+       end = INT_GET(sf->hdr.count, ARCH_CONVERT);
+       for (i = 0; i < end; sfe = XFS_ATTR_SF_NEXTENTRY(sfe),
                                        base += size, i++) {
                size = XFS_ATTR_SF_ENTSIZE(sfe);
                if (sfe->namelen != args->namelen)
@@ -237,19 +314,51 @@ xfs_attr_shortform_remove(xfs_da_args_t *args)
                        continue;
                break;
        }
-       if (i == INT_GET(sf->hdr.count, ARCH_CONVERT))
+       if (i == end)
                return(XFS_ERROR(ENOATTR));
 
+       /*
+        * Fix up the attribute fork data, covering the hole
+        */
        end = base + size;
        totsize = INT_GET(sf->hdr.totsize, ARCH_CONVERT);
-       if (end != totsize) {
-               memmove(&((char *)sf)[base], &((char *)sf)[end],
-                                                       totsize - end);
-       }
+       if (end != totsize)
+               memmove(&((char *)sf)[base], &((char *)sf)[end], totsize - end);
        INT_MOD(sf->hdr.count, ARCH_CONVERT, -1);
        INT_MOD(sf->hdr.totsize, ARCH_CONVERT, -size);
-       xfs_idata_realloc(dp, -size, XFS_ATTR_FORK);
-       xfs_trans_log_inode(args->trans, dp, XFS_ILOG_CORE | XFS_ILOG_ADATA);
+
+       /*
+        * Fix up the start offset of the attribute fork
+        */
+       totsize -= size;
+       if (totsize == sizeof(xfs_attr_sf_hdr_t) && !args->addname) {
+               /*
+                * Last attribute now removed, revert to original
+                * inode format making all literal area available
+                * to the data fork once more.
+                */
+               xfs_idestroy_fork(dp, XFS_ATTR_FORK);
+               dp->i_d.di_forkoff = 0;
+               dp->i_d.di_aformat = XFS_DINODE_FMT_EXTENTS;
+               ASSERT(dp->i_d.di_anextents == 0);
+               ASSERT(dp->i_afp == NULL);
+               dp->i_df.if_ext_max =
+                       XFS_IFORK_DSIZE(dp) / (uint)sizeof(xfs_bmbt_rec_t);
+               xfs_trans_log_inode(args->trans, dp, XFS_ILOG_CORE);
+       } else {
+               xfs_idata_realloc(dp, -size, XFS_ATTR_FORK);
+               dp->i_d.di_forkoff = xfs_attr_shortform_bytesfit(dp, totsize);
+               ASSERT(dp->i_d.di_forkoff);
+               ASSERT(totsize > sizeof(xfs_attr_sf_hdr_t) || args->addname);
+               dp->i_afp->if_ext_max =
+                       XFS_IFORK_ASIZE(dp) / (uint)sizeof(xfs_bmbt_rec_t);
+               dp->i_df.if_ext_max =
+                       XFS_IFORK_DSIZE(dp) / (uint)sizeof(xfs_bmbt_rec_t);
+               xfs_trans_log_inode(args->trans, dp,
+                                       XFS_ILOG_CORE | XFS_ILOG_ADATA);
+       }
+
+       xfs_sbversion_add_attr2(mp, args->trans);
 
        return(0);
 }
@@ -649,14 +758,16 @@ xfs_attr_shortform_allfit(xfs_dabuf_t *bp, xfs_inode_t *dp)
                                + name_loc->namelen
                                + INT_GET(name_loc->valuelen, ARCH_CONVERT);
        }
-       return( bytes < XFS_IFORK_ASIZE(dp) );
+       if (bytes == sizeof(struct xfs_attr_sf_hdr))
+               return(-1);
+       return(xfs_attr_shortform_bytesfit(dp, bytes));
 }
 
 /*
  * Convert a leaf attribute list to shortform attribute list
  */
 int
-xfs_attr_leaf_to_shortform(xfs_dabuf_t *bp, xfs_da_args_t *args)
+xfs_attr_leaf_to_shortform(xfs_dabuf_t *bp, xfs_da_args_t *args, int forkoff)
 {
        xfs_attr_leafblock_t *leaf;
        xfs_attr_leaf_entry_t *entry;
@@ -683,9 +794,25 @@ xfs_attr_leaf_to_shortform(xfs_dabuf_t *bp, xfs_da_args_t *args)
        error = xfs_da_shrink_inode(args, 0, bp);
        if (error)
                goto out;
-       error = xfs_attr_shortform_create(args);
-       if (error)
+
+       if (forkoff == -1) {
+               /*
+                * Last attribute was removed, revert to original
+                * inode format making all literal area available
+                * to the data fork once more.
+                */
+               xfs_idestroy_fork(dp, XFS_ATTR_FORK);
+               dp->i_d.di_forkoff = 0;
+               dp->i_d.di_aformat = XFS_DINODE_FMT_EXTENTS;
+               ASSERT(dp->i_d.di_anextents == 0);
+               ASSERT(dp->i_afp == NULL);
+               dp->i_df.if_ext_max =
+                       XFS_IFORK_DSIZE(dp) / (uint)sizeof(xfs_bmbt_rec_t);
+               xfs_trans_log_inode(args->trans, dp, XFS_ILOG_CORE);
                goto out;
+       }
+
+       xfs_attr_shortform_create(args);
 
        /*
         * Copy the attributes
@@ -713,7 +840,7 @@ xfs_attr_leaf_to_shortform(xfs_dabuf_t *bp, xfs_da_args_t *args)
                nargs.hashval = INT_GET(entry->hashval, ARCH_CONVERT);
                nargs.flags = (entry->flags & XFS_ATTR_SECURE) ? ATTR_SECURE :
                              ((entry->flags & XFS_ATTR_ROOT) ? ATTR_ROOT : 0);
-               xfs_attr_shortform_add(&nargs);
+               xfs_attr_shortform_add(&nargs, forkoff);
        }
        error = 0;