that force a client to purge cache pages when a server requests it.
Signed-off-by: Pavel Shilovsky <pshilovsky@samba.org>
Signed-off-by: Steve French <smfrench@gmail.com>
        cifs_inode->server_eof = 0;
        cifs_inode->uniqueid = 0;
        cifs_inode->createtime = 0;
+       cifs_inode->epoch = 0;
 #ifdef CONFIG_CIFS_SMB2
        get_random_bytes(cifs_inode->lease_key, SMB2_LEASE_KEY_SIZE);
 #endif
 
        /* if we can do cache read operations */
        bool (*is_read_op)(__u32);
        /* set oplock level for the inode */
-       void (*set_oplock_level)(struct cifsInodeInfo *, __u32);
+       void (*set_oplock_level)(struct cifsInodeInfo *, __u32, unsigned int,
+                                bool *);
        /* create lease context buffer for CREATE request */
        char * (*create_lease_buf)(u8 *, u8);
-       /* parse lease context buffer and return oplock info */
-       __u8 (*parse_lease_buf)(void *);
+       /* parse lease context buffer and return oplock/epoch info */
+       __u8 (*parse_lease_buf)(void *, unsigned int *);
 };
 
 struct smb_version_values {
        __u8 lease_key[SMB2_LEASE_KEY_SIZE];    /* lease key for smb2 */
 #endif
        struct cifs_pending_open *pending_open;
+       unsigned int epoch;
+       bool purge_cache;
 };
 
 struct cifs_fid_locks {
 
 #define CIFS_CACHE_READ_FLG    1
 #define CIFS_CACHE_HANDLE_FLG  2
+#define CIFS_CACHE_RH_FLG      (CIFS_CACHE_READ_FLG | CIFS_CACHE_HANDLE_FLG)
 #define CIFS_CACHE_WRITE_FLG   4
+#define CIFS_CACHE_RW_FLG      (CIFS_CACHE_READ_FLG | CIFS_CACHE_WRITE_FLG)
+#define CIFS_CACHE_RHW_FLG     (CIFS_CACHE_RW_FLG | CIFS_CACHE_HANDLE_FLG)
 
 #define CIFS_CACHE_READ(cinode) (cinode->oplock & CIFS_CACHE_READ_FLG)
 #define CIFS_CACHE_HANDLE(cinode) (cinode->oplock & CIFS_CACHE_HANDLE_FLG)
        struct list_head openFileList;
        __u32 cifsAttrs; /* e.g. DOS archive bit, sparse, compressed, system */
        unsigned int oplock;            /* oplock/lease level we have */
+       unsigned int epoch;             /* used to track lease state changes */
        bool delete_pending;            /* DELETE_ON_CLOSE is set */
        bool invalid_mapping;           /* pagecache is invalid */
        unsigned long time;             /* jiffies of last update of inode */
 
                oplock = fid->pending_open->oplock;
        list_del(&fid->pending_open->olist);
 
+       fid->purge_cache = false;
        server->ops->set_fid(cfile, fid, oplock);
 
        list_add(&cfile->tlist, &tcon->openFileList);
                list_add_tail(&cfile->flist, &cinode->openFileList);
        spin_unlock(&cifs_file_list_lock);
 
+       if (fid->purge_cache)
+               cifs_invalidate_mapping(inode);
+
        file->private_data = cfile;
        return cfile;
 }
 
        __u8 lease_state;
        struct list_head *tmp;
        struct cifsFileInfo *cfile;
+       struct TCP_Server_Info *server = tcon->ses->server;
        struct cifs_pending_open *open;
        struct cifsInodeInfo *cinode;
        int ack_req = le32_to_cpu(rsp->Flags &
                cifs_dbg(FYI, "lease key match, lease break 0x%d\n",
                         le32_to_cpu(rsp->NewLeaseState));
 
-               tcon->ses->server->ops->set_oplock_level(cinode, lease_state);
+               server->ops->set_oplock_level(cinode, lease_state, 0, NULL);
 
                if (ack_req)
                        cfile->oplock_break_cancelled = false;
                                        cfile->oplock_break_cancelled = false;
 
                                server->ops->set_oplock_level(cinode,
-                                 rsp->OplockLevel ? SMB2_OPLOCK_LEVEL_II : 0);
+                                 rsp->OplockLevel ? SMB2_OPLOCK_LEVEL_II : 0,
+                                 0, NULL);
 
                                queue_work(cifsiod_wq, &cfile->oplock_break);
 
 
 
        cfile->fid.persistent_fid = fid->persistent_fid;
        cfile->fid.volatile_fid = fid->volatile_fid;
-       server->ops->set_oplock_level(cinode, oplock);
+       server->ops->set_oplock_level(cinode, oplock, fid->epoch,
+                                     &fid->purge_cache);
        cinode->can_cache_brlcks = CIFS_CACHE_WRITE(cinode);
 }
 
 }
 
 static void
-smb2_set_oplock_level(struct cifsInodeInfo *cinode, __u32 oplock)
+smb2_set_oplock_level(struct cifsInodeInfo *cinode, __u32 oplock,
+                     unsigned int epoch, bool *purge_cache)
 {
        oplock &= 0xFF;
        if (oplock == SMB2_OPLOCK_LEVEL_NOCHANGE)
                return;
        if (oplock == SMB2_OPLOCK_LEVEL_BATCH) {
-               cinode->oplock = CIFS_CACHE_READ_FLG | CIFS_CACHE_WRITE_FLG |
-                                CIFS_CACHE_HANDLE_FLG;
+               cinode->oplock = CIFS_CACHE_RHW_FLG;
                cifs_dbg(FYI, "Batch Oplock granted on inode %p\n",
                         &cinode->vfs_inode);
        } else if (oplock == SMB2_OPLOCK_LEVEL_EXCLUSIVE) {
-               cinode->oplock = CIFS_CACHE_READ_FLG | CIFS_CACHE_WRITE_FLG;
+               cinode->oplock = CIFS_CACHE_RW_FLG;
                cifs_dbg(FYI, "Exclusive Oplock granted on inode %p\n",
                         &cinode->vfs_inode);
        } else if (oplock == SMB2_OPLOCK_LEVEL_II) {
 }
 
 static void
-smb21_set_oplock_level(struct cifsInodeInfo *cinode, __u32 oplock)
+smb21_set_oplock_level(struct cifsInodeInfo *cinode, __u32 oplock,
+                      unsigned int epoch, bool *purge_cache)
 {
        char message[5] = {0};
 
                 &cinode->vfs_inode);
 }
 
+static void
+smb3_set_oplock_level(struct cifsInodeInfo *cinode, __u32 oplock,
+                     unsigned int epoch, bool *purge_cache)
+{
+       unsigned int old_oplock = cinode->oplock;
+
+       smb21_set_oplock_level(cinode, oplock, epoch, purge_cache);
+
+       if (purge_cache) {
+               *purge_cache = false;
+               if (old_oplock == CIFS_CACHE_READ_FLG) {
+                       if (cinode->oplock == CIFS_CACHE_READ_FLG &&
+                           (epoch - cinode->epoch > 0))
+                               *purge_cache = true;
+                       else if (cinode->oplock == CIFS_CACHE_RH_FLG &&
+                                (epoch - cinode->epoch > 1))
+                               *purge_cache = true;
+                       else if (cinode->oplock == CIFS_CACHE_RHW_FLG &&
+                                (epoch - cinode->epoch > 1))
+                               *purge_cache = true;
+                       else if (cinode->oplock == 0 &&
+                                (epoch - cinode->epoch > 0))
+                               *purge_cache = true;
+               } else if (old_oplock == CIFS_CACHE_RH_FLG) {
+                       if (cinode->oplock == CIFS_CACHE_RH_FLG &&
+                           (epoch - cinode->epoch > 0))
+                               *purge_cache = true;
+                       else if (cinode->oplock == CIFS_CACHE_RHW_FLG &&
+                                (epoch - cinode->epoch > 1))
+                               *purge_cache = true;
+               }
+               cinode->epoch = epoch;
+       }
+}
+
 static bool
 smb2_is_read_op(__u32 oplock)
 {
 }
 
 static __u8
-smb2_parse_lease_buf(void *buf)
+smb2_parse_lease_buf(void *buf, unsigned int *epoch)
 {
        struct create_lease *lc = (struct create_lease *)buf;
 
+       *epoch = 0; /* not used */
        if (lc->lcontext.LeaseFlags & SMB2_LEASE_FLAG_BREAK_IN_PROGRESS)
                return SMB2_OPLOCK_LEVEL_NOCHANGE;
        return le32_to_cpu(lc->lcontext.LeaseState);
 }
 
 static __u8
-smb3_parse_lease_buf(void *buf)
+smb3_parse_lease_buf(void *buf, unsigned int *epoch)
 {
        struct create_lease_v2 *lc = (struct create_lease_v2 *)buf;
 
+       *epoch = le16_to_cpu(lc->lcontext.Epoch);
        if (lc->lcontext.LeaseFlags & SMB2_LEASE_FLAG_BREAK_IN_PROGRESS)
                return SMB2_OPLOCK_LEVEL_NOCHANGE;
        return le32_to_cpu(lc->lcontext.LeaseState);
        .generate_signingkey = generate_smb3signingkey,
        .calc_signature = smb3_calc_signature,
        .is_read_op = smb21_is_read_op,
-       .set_oplock_level = smb21_set_oplock_level,
+       .set_oplock_level = smb3_set_oplock_level,
        .create_lease_buf = smb3_create_lease_buf,
        .parse_lease_buf = smb3_parse_lease_buf,
 };
 
 }
 
 static __u8
-parse_lease_state(struct TCP_Server_Info *server, struct smb2_create_rsp *rsp)
+parse_lease_state(struct TCP_Server_Info *server, struct smb2_create_rsp *rsp,
+                 unsigned int *epoch)
 {
        char *data_offset;
        struct create_context *cc;
                        next = le32_to_cpu(cc->Next);
                        continue;
                }
-               return server->ops->parse_lease_buf(cc);
+               return server->ops->parse_lease_buf(cc, epoch);
        } while (next != 0);
 
        return 0;
        }
 
        if (rsp->OplockLevel == SMB2_OPLOCK_LEVEL_LEASE)
-               *oplock = parse_lease_state(server, rsp);
+               *oplock = parse_lease_state(server, rsp, &oparms->fid->epoch);
        else
                *oplock = rsp->OplockLevel;
 creat_exit: