Preserve inode mtime/ctime in .writepage()
[zfs.git] / module / zfs / zfs_vnops.c
index 3003302..415ba71 100644 (file)
@@ -218,11 +218,32 @@ zfs_close(struct inode *ip, int flag, cred_t *cr)
 {
        znode_t *zp = ITOZ(ip);
        zfs_sb_t *zsb = ITOZSB(ip);
+       int error = 0;
 
        ZFS_ENTER(zsb);
        ZFS_VERIFY_ZP(zp);
 
        /*
+        * When closing an mmap()'ed file ensure the inode atime, mtime, and
+        * ctime are written to disk.  These values may have been updated in
+        * memory by filemap_page_mkwrite() bit are not yet reflected in the
+        * znode since writepage() may occur after the close.
+        */
+       if (zp->z_is_mapped) {
+               vattr_t *vap;
+
+               vap = kmem_zalloc(sizeof(vattr_t), KM_SLEEP);
+               vap->va_mask = ATTR_ATIME | ATTR_MTIME | ATTR_CTIME;
+               vap->va_atime = ip->i_atime;
+               vap->va_mtime = ip->i_mtime;
+               vap->va_ctime = ip->i_ctime;
+
+               error = zfs_setattr(ip, vap, 0, cr);
+
+               kmem_free(vap, sizeof(vattr_t));
+       }
+
+       /*
         * Zero the synchronous opens in the znode.  Under Linux the
         * zfs_close() hook is not symmetric with zfs_open(), it is
         * only called once when the last reference is dropped.
@@ -235,7 +256,7 @@ zfs_close(struct inode *ip, int flag, cred_t *cr)
                VERIFY(zfs_vscan(ip, cr, 1) == 0);
 
        ZFS_EXIT(zsb);
-       return (0);
+       return (error);
 }
 EXPORT_SYMBOL(zfs_close);
 
@@ -3790,7 +3811,8 @@ zfs_putpage(struct inode *ip, struct page *pp, struct writeback_control *wbc)
        zfs_sb_t        *zsb = ITOZSB(ip);
        loff_t          offset;
        loff_t          pgoff;
-        unsigned int   pglen;
+       unsigned int    pglen;
+       rl_t            *rl;
        dmu_tx_t        *tx;
        caddr_t         va;
        int             err = 0;
@@ -3799,6 +3821,8 @@ zfs_putpage(struct inode *ip, struct page *pp, struct writeback_control *wbc)
        int             cnt = 0;
        int             sync;
 
+       ZFS_ENTER(zsb);
+       ZFS_VERIFY_ZP(zp);
 
        ASSERT(PageLocked(pp));
 
@@ -3810,6 +3834,7 @@ zfs_putpage(struct inode *ip, struct page *pp, struct writeback_control *wbc)
        /* Page is beyond end of file */
        if (pgoff >= offset) {
                unlock_page(pp);
+               ZFS_EXIT(zsb);
                return (0);
        }
 
@@ -3832,6 +3857,7 @@ zfs_putpage(struct inode *ip, struct page *pp, struct writeback_control *wbc)
        set_page_writeback(pp);
        unlock_page(pp);
 
+       rl = zfs_range_lock(zp, pgoff, pglen, RL_WRITER);
        tx = dmu_tx_create(zsb->z_os);
 
        sync = ((zsb->z_os->os_sync == ZFS_SYNC_ALWAYS) ||
@@ -3848,7 +3874,18 @@ zfs_putpage(struct inode *ip, struct page *pp, struct writeback_control *wbc)
                if (err == ERESTART)
                        dmu_tx_wait(tx);
 
+               /* Will call all registered commit callbacks */
                dmu_tx_abort(tx);
+
+               /*
+                * For the synchronous case the commit callback must be
+                * explicitly called because there is no registered callback.
+                */
+               if (sync)
+                       zfs_putpage_commit_cb(pp, ECANCELED);
+
+               zfs_range_unlock(rl);
+               ZFS_EXIT(zsb);
                return (err);
        }
 
@@ -3860,17 +3897,26 @@ zfs_putpage(struct inode *ip, struct page *pp, struct writeback_control *wbc)
        SA_ADD_BULK_ATTR(bulk, cnt, SA_ZPL_MTIME(zsb), NULL, &mtime, 16);
        SA_ADD_BULK_ATTR(bulk, cnt, SA_ZPL_CTIME(zsb), NULL, &ctime, 16);
        SA_ADD_BULK_ATTR(bulk, cnt, SA_ZPL_FLAGS(zsb), NULL, &zp->z_pflags, 8);
-       zfs_tstamp_update_setup(zp, CONTENT_MODIFIED, mtime, ctime, B_TRUE);
-       zfs_log_write(zsb->z_log, tx, TX_WRITE, zp, pgoff, pglen, 0);
 
+       /* Preserve the mtime and ctime provided by the inode */
+       ZFS_TIME_ENCODE(&ip->i_mtime, mtime);
+       ZFS_TIME_ENCODE(&ip->i_ctime, ctime);
+       zp->z_atime_dirty = 0;
+       zp->z_seq++;
+
+       err = sa_bulk_update(zp->z_sa_hdl, bulk, cnt, tx);
+
+       zfs_log_write(zsb->z_log, tx, TX_WRITE, zp, pgoff, pglen, 0);
        dmu_tx_commit(tx);
-       ASSERT3S(err, ==, 0);
+
+       zfs_range_unlock(rl);
 
        if (sync) {
                zil_commit(zsb->z_log, zp->z_id);
                zfs_putpage_commit_cb(pp, err);
        }
 
+       ZFS_EXIT(zsb);
        return (err);
 }