diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index 3e32ad18e6c2..05f2ab6d345a 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -3788,12 +3788,49 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon, smb2_set_sparse(xid, tcon, cfile, inode, false); new_eof = off + len; + + qrc = SMB2_query_info(xid, tcon, + cfile->fid.persistent_fid, + cfile->fid.volatile_fid, &file_inf); + if (qrc == 0) + asize = le64_to_cpu(file_inf.AllocationSize); + + /* + * FILE_ALLOCATION_INFORMATION can only describe allocation up to + * new_eof. Some servers may accept it without allocating blocks, + * so refresh AllocationSize before updating i_blocks. + */ + if (off == 0 || off == old_eof) { + if (qrc || asize < new_eof) { + rc = SMB2_set_allocation(xid, tcon, + cfile->fid.persistent_fid, + cfile->fid.volatile_fid, + cfile->pid, new_eof); + if (rc) + goto out; + } + } + rc = SMB2_set_eof(xid, tcon, cfile->fid.persistent_fid, cfile->fid.volatile_fid, cfile->pid, new_eof); - if (rc == 0) { - netfs_resize_file(&cifsi->netfs, new_eof, true); - cifs_setsize(inode, new_eof); + if (rc) + goto out; + + netfs_resize_file(&cifsi->netfs, new_eof, true); + cifs_setsize(inode, new_eof); + + qrc = SMB2_query_info(xid, tcon, + cfile->fid.persistent_fid, + cfile->fid.volatile_fid, &file_inf); + spin_lock(&inode->i_lock); + if (qrc == 0) { + asize = le64_to_cpu(file_inf.AllocationSize); + if (asize >= new_eof) + inode->i_blocks = CIFS_INO_BLOCKS(asize); + } else { + cifsi->time = 0; } + spin_unlock(&inode->i_lock); goto out; } diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c index 8f83ab377db1..4ce165e40657 100644 --- a/fs/smb/client/smb2pdu.c +++ b/fs/smb/client/smb2pdu.c @@ -5949,6 +5949,25 @@ SMB2_set_eof(const unsigned int xid, struct cifs_tcon *tcon, u64 persistent_fid, 0, 1, &data, &size); } +int +SMB2_set_allocation(const unsigned int xid, struct cifs_tcon *tcon, + u64 persistent_fid, u64 volatile_fid, u32 pid, + loff_t allocation_size) +{ + struct smb2_file_alloc_info info; + void *data; + unsigned int size; + + info.AllocationSize = cpu_to_le64(allocation_size); + + data = &info; + size = sizeof(struct smb2_file_alloc_info); + + return send_set_info(xid, tcon, persistent_fid, volatile_fid, + pid, FILE_ALLOCATION_INFORMATION, SMB2_O_INFO_FILE, + 0, 1, &data, &size); +} + int SMB2_set_acl(const unsigned int xid, struct cifs_tcon *tcon, u64 persistent_fid, u64 volatile_fid, diff --git a/fs/smb/client/smb2proto.h b/fs/smb/client/smb2proto.h index e01effe45ae2..2e9f70096825 100644 --- a/fs/smb/client/smb2proto.h +++ b/fs/smb/client/smb2proto.h @@ -204,6 +204,9 @@ void SMB2_query_directory_free(struct smb_rqst *rqst); int SMB2_set_eof(const unsigned int xid, struct cifs_tcon *tcon, u64 persistent_fid, u64 volatile_fid, u32 pid, loff_t new_eof); +int SMB2_set_allocation(const unsigned int xid, struct cifs_tcon *tcon, + u64 persistent_fid, u64 volatile_fid, u32 pid, + loff_t allocation_size); int SMB2_set_info_init(struct cifs_tcon *tcon, struct TCP_Server_Info *server, struct smb_rqst *rqst, u64 persistent_fid, u64 volatile_fid, u32 pid, u8 info_class, u8 info_type, diff --git a/fs/smb/common/fscc.h b/fs/smb/common/fscc.h index 859849a42fec..941db5a95564 100644 --- a/fs/smb/common/fscc.h +++ b/fs/smb/common/fscc.h @@ -283,6 +283,11 @@ struct smb2_file_eof_info { /* encoding of request for level 10 */ __le64 EndOfFile; /* new end of file value */ } __packed; /* level 20 Set */ +/* See MS-FSCC 2.4.4 */ +struct smb2_file_alloc_info { /* encoding of request for level 19 */ + __le64 AllocationSize; +} __packed; + /* See MS-FSCC 2.4.15 */ typedef struct { __le32 NextEntryOffset; diff --git a/fs/smb/server/smb2pdu.h b/fs/smb/server/smb2pdu.h index c2512dbcdec8..aa06c8c905f1 100644 --- a/fs/smb/server/smb2pdu.h +++ b/fs/smb/server/smb2pdu.h @@ -212,10 +212,6 @@ struct smb2_file_ea_info { __le32 EASize; } __packed; -struct smb2_file_alloc_info { - __le64 AllocationSize; -} __packed; - struct smb2_file_disposition_info { __u8 DeletePending; } __packed;