diff options
| -rw-r--r-- | MAINTAINERS | 1 | ||||
| -rw-r--r-- | fs/smb/client/Makefile | 2 | ||||
| -rw-r--r-- | fs/smb/client/cifs_debug.c | 34 | ||||
| -rw-r--r-- | fs/smb/client/cifsfs.c | 2 | ||||
| -rw-r--r-- | fs/smb/client/cifsfs.h | 4 | ||||
| -rw-r--r-- | fs/smb/client/cifsglob.h | 61 | ||||
| -rw-r--r-- | fs/smb/client/cifsproto.h | 20 | ||||
| -rw-r--r-- | fs/smb/client/cifssmb.c | 4 | ||||
| -rw-r--r-- | fs/smb/client/connect.c | 4 | ||||
| -rw-r--r-- | fs/smb/client/file.c | 303 | ||||
| -rw-r--r-- | fs/smb/client/fs_context.c | 64 | ||||
| -rw-r--r-- | fs/smb/client/fs_context.h | 11 | ||||
| -rw-r--r-- | fs/smb/client/inode.c | 135 | ||||
| -rw-r--r-- | fs/smb/client/ioctl.c | 5 | ||||
| -rw-r--r-- | fs/smb/client/misc.c | 34 | ||||
| -rw-r--r-- | fs/smb/client/readdir.c | 22 | ||||
| -rw-r--r-- | fs/smb/client/reparse.c | 532 | ||||
| -rw-r--r-- | fs/smb/client/reparse.h | 113 | ||||
| -rw-r--r-- | fs/smb/client/smb2glob.h | 3 | ||||
| -rw-r--r-- | fs/smb/client/smb2inode.c | 487 | ||||
| -rw-r--r-- | fs/smb/client/smb2ops.c | 254 | ||||
| -rw-r--r-- | fs/smb/client/smb2pdu.c | 37 | ||||
| -rw-r--r-- | fs/smb/client/smb2pdu.h | 36 | ||||
| -rw-r--r-- | fs/smb/client/smb2proto.h | 15 | ||||
| -rw-r--r-- | fs/smb/client/trace.h | 34 | ||||
| -rw-r--r-- | fs/smb/common/smb2pdu.h | 49 | ||||
| -rw-r--r-- | fs/smb/common/smbfsctl.h | 6 |
27 files changed, 1565 insertions, 707 deletions
diff --git a/MAINTAINERS b/MAINTAINERS index 4bc35329837a..2696082d458e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5255,6 +5255,7 @@ R: Paulo Alcantara <pc@manguebit.com> (DFS, global name space) R: Ronnie Sahlberg <ronniesahlberg@gmail.com> (directory leases, sparse files) R: Shyam Prasad N <sprasad@microsoft.com> (multichannel) R: Tom Talpey <tom@talpey.com> (RDMA, smbdirect) +R: Bharath SM <bharathsm@microsoft.com> (deferred close, directory leases) L: linux-cifs@vger.kernel.org L: samba-technical@lists.samba.org (moderated for non-subscribers) S: Supported diff --git a/fs/smb/client/Makefile b/fs/smb/client/Makefile index 0b07eb94c93b..e11985f2460b 100644 --- a/fs/smb/client/Makefile +++ b/fs/smb/client/Makefile @@ -12,7 +12,7 @@ cifs-y := trace.o cifsfs.o cifs_debug.o connect.o dir.o file.o \ smb2ops.o smb2maperror.o smb2transport.o \ smb2misc.o smb2pdu.o smb2inode.o smb2file.o cifsacl.o fs_context.o \ dns_resolve.o cifs_spnego_negtokeninit.asn1.o asn1.o \ - namespace.o + namespace.o reparse.o $(obj)/asn1.o: $(obj)/cifs_spnego_negtokeninit.asn1.h diff --git a/fs/smb/client/cifs_debug.c b/fs/smb/client/cifs_debug.c index 3e4209f41c18..226d4835c92d 100644 --- a/fs/smb/client/cifs_debug.c +++ b/fs/smb/client/cifs_debug.c @@ -278,6 +278,24 @@ static int cifs_debug_files_proc_show(struct seq_file *m, void *v) return 0; } +static __always_inline const char *compression_alg_str(__le16 alg) +{ + switch (alg) { + case SMB3_COMPRESS_NONE: + return "NONE"; + case SMB3_COMPRESS_LZNT1: + return "LZNT1"; + case SMB3_COMPRESS_LZ77: + return "LZ77"; + case SMB3_COMPRESS_LZ77_HUFF: + return "LZ77-Huffman"; + case SMB3_COMPRESS_PATTERN: + return "Pattern_V1"; + default: + return "invalid"; + } +} + static int cifs_debug_data_proc_show(struct seq_file *m, void *v) { struct mid_q_entry *mid_entry; @@ -423,12 +441,6 @@ skip_rdma: server->echo_credits, server->oplock_credits, server->dialect); - if (server->compress_algorithm == SMB3_COMPRESS_LZNT1) - seq_printf(m, " COMPRESS_LZNT1"); - else if (server->compress_algorithm == SMB3_COMPRESS_LZ77) - seq_printf(m, " COMPRESS_LZ77"); - else if (server->compress_algorithm == SMB3_COMPRESS_LZ77_HUFF) - seq_printf(m, " COMPRESS_LZ77_HUFF"); if (server->sign) seq_printf(m, " signed"); if (server->posix_ext_supported) @@ -460,6 +472,14 @@ skip_rdma: server->leaf_fullpath); } + seq_puts(m, "\nCompression: "); + if (!server->compression.requested) + seq_puts(m, "disabled on mount"); + else if (server->compression.enabled) + seq_printf(m, "enabled (%s)", compression_alg_str(server->compression.alg)); + else + seq_puts(m, "disabled (not supported by this server)"); + seq_printf(m, "\n\n\tSessions: "); i = 0; list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) { @@ -488,6 +508,8 @@ skip_rdma: ses->ses_count, ses->serverOS, ses->serverNOS, ses->capabilities, ses->ses_status); } + if (ses->expired_pwd) + seq_puts(m, "password no longer valid "); spin_unlock(&ses->ses_lock); seq_printf(m, "\n\tSecurity type: %s ", diff --git a/fs/smb/client/cifsfs.c b/fs/smb/client/cifsfs.c index e0d8c79cdde1..81d9aafd2210 100644 --- a/fs/smb/client/cifsfs.c +++ b/fs/smb/client/cifsfs.c @@ -673,6 +673,8 @@ cifs_show_options(struct seq_file *s, struct dentry *root) seq_printf(s, ",backupgid=%u", from_kgid_munged(&init_user_ns, cifs_sb->ctx->backupgid)); + seq_show_option(s, "reparse", + cifs_reparse_type_str(cifs_sb->ctx->reparse_type)); seq_printf(s, ",rsize=%u", cifs_sb->ctx->rsize); seq_printf(s, ",wsize=%u", cifs_sb->ctx->wsize); diff --git a/fs/smb/client/cifsfs.h b/fs/smb/client/cifsfs.h index 685f7d1139c6..ca55d01117c8 100644 --- a/fs/smb/client/cifsfs.h +++ b/fs/smb/client/cifsfs.h @@ -152,6 +152,6 @@ extern const struct export_operations cifs_export_ops; #endif /* CONFIG_CIFS_NFSD_EXPORT */ /* when changing internal version - update following two lines at same time */ -#define SMB3_PRODUCT_BUILD 47 -#define CIFS_VERSION "2.47" +#define SMB3_PRODUCT_BUILD 48 +#define CIFS_VERSION "2.48" #endif /* _CIFSFS_H */ diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h index 53c75cfb33ab..8be62ed053a2 100644 --- a/fs/smb/client/cifsglob.h +++ b/fs/smb/client/cifsglob.h @@ -153,6 +153,24 @@ enum securityEnum { Kerberos, /* Kerberos via SPNEGO */ }; +enum cifs_reparse_type { + CIFS_REPARSE_TYPE_NFS, + CIFS_REPARSE_TYPE_WSL, + CIFS_REPARSE_TYPE_DEFAULT = CIFS_REPARSE_TYPE_NFS, +}; + +static inline const char *cifs_reparse_type_str(enum cifs_reparse_type type) +{ + switch (type) { + case CIFS_REPARSE_TYPE_NFS: + return "nfs"; + case CIFS_REPARSE_TYPE_WSL: + return "wsl"; + default: + return "unknown"; + } +} + struct session_key { unsigned int len; char *response; @@ -208,6 +226,10 @@ struct cifs_open_info_data { struct reparse_posix_data *posix; }; } reparse; + struct { + __u8 eas[SMB2_WSL_MAX_QUERY_EA_RESP_SIZE]; + unsigned int eas_len; + } wsl; char *symlink_target; struct cifs_sid posix_owner; struct cifs_sid posix_group; @@ -217,19 +239,6 @@ struct cifs_open_info_data { }; }; -static inline bool cifs_open_data_reparse(struct cifs_open_info_data *data) -{ - struct smb2_file_all_info *fi = &data->fi; - u32 attrs = le32_to_cpu(fi->Attributes); - bool ret; - - ret = data->reparse_point || (attrs & ATTR_REPARSE); - if (ret) - attrs |= ATTR_REPARSE; - fi->Attributes = cpu_to_le32(attrs); - return ret; -} - /* ***************************************************************** * Except the CIFS PDUs themselves all the @@ -371,7 +380,8 @@ struct smb_version_operations { struct cifs_open_info_data *data); /* set size by path */ int (*set_path_size)(const unsigned int, struct cifs_tcon *, - const char *, __u64, struct cifs_sb_info *, bool); + const char *, __u64, struct cifs_sb_info *, bool, + struct dentry *); /* set size by file handle */ int (*set_file_size)(const unsigned int, struct cifs_tcon *, struct cifsFileInfo *, __u64, bool); @@ -401,7 +411,7 @@ struct smb_version_operations { struct cifs_sb_info *); /* unlink file */ int (*unlink)(const unsigned int, struct cifs_tcon *, const char *, - struct cifs_sb_info *); + struct cifs_sb_info *, struct dentry *); /* open, rename and delete file */ int (*rename_pending_delete)(const char *, struct dentry *, const unsigned int); @@ -759,7 +769,11 @@ struct TCP_Server_Info { unsigned int max_write; unsigned int min_offload; unsigned int retrans; - __le16 compress_algorithm; + struct { + bool requested; /* "compress" mount option set*/ + bool enabled; /* actually negotiated with server */ + __le16 alg; /* preferred alg negotiated with server */ + } compression; __u16 signing_algorithm; __le16 cipher_type; /* save initital negprot hash */ @@ -1066,6 +1080,7 @@ struct cifs_ses { enum securityEnum sectype; /* what security flavor was specified? */ bool sign; /* is signing required? */ bool domainAuto:1; + bool expired_pwd; /* track if access denied or expired pwd so can know if need to update */ unsigned int flags; __u16 session_flags; __u8 smb3signingkey[SMB3_SIGN_KEY_SIZE]; @@ -1379,6 +1394,7 @@ struct cifs_open_parms { umode_t mode; bool reconnect:1; bool replay:1; /* indicates that this open is for a replay */ + struct kvec *ea_cctx; }; struct cifs_fid { @@ -1420,6 +1436,7 @@ struct cifsFileInfo { bool invalidHandle:1; /* file closed via session abend */ bool swapfile:1; bool oplock_break_cancelled:1; + bool status_file_deleted:1; /* file has been deleted */ unsigned int oplock_epoch; /* epoch from the lease break */ __u32 oplock_level; /* oplock/lease level from the lease break */ int count; @@ -2277,6 +2294,17 @@ static inline void cifs_sg_set_buf(struct sg_table *sgtable, } } +#define CIFS_OPARMS(_cifs_sb, _tcon, _path, _da, _cd, _co, _mode) \ + ((struct cifs_open_parms) { \ + .tcon = _tcon, \ + .path = _path, \ + .desired_access = (_da), \ + .disposition = (_cd), \ + .create_options = cifs_create_options(_cifs_sb, (_co)), \ + .mode = (_mode), \ + .cifs_sb = _cifs_sb, \ + }) + struct smb2_compound_vars { struct cifs_open_parms oparms; struct kvec rsp_iov[MAX_COMPOUND]; @@ -2288,6 +2316,7 @@ struct smb2_compound_vars { struct kvec close_iov; struct smb2_file_rename_info rename_info; struct smb2_file_link_info link_info; + struct kvec ea_iov; }; #endif /* _CIFS_GLOB_H */ diff --git a/fs/smb/client/cifsproto.h b/fs/smb/client/cifsproto.h index a841bf4967fa..0723e1b57256 100644 --- a/fs/smb/client/cifsproto.h +++ b/fs/smb/client/cifsproto.h @@ -144,7 +144,8 @@ extern int cifs_reconnect(struct TCP_Server_Info *server, extern int checkSMB(char *buf, unsigned int len, struct TCP_Server_Info *srvr); extern bool is_valid_oplock_break(char *, struct TCP_Server_Info *); extern bool backup_cred(struct cifs_sb_info *); -extern bool is_size_safe_to_change(struct cifsInodeInfo *, __u64 eof); +extern bool is_size_safe_to_change(struct cifsInodeInfo *cifsInode, __u64 eof, + bool from_readdir); extern void cifs_update_eof(struct cifsInodeInfo *cifsi, loff_t offset, unsigned int bytes_written); extern struct cifsFileInfo *find_writable_file(struct cifsInodeInfo *, int); @@ -201,17 +202,14 @@ extern void cifs_unix_basic_to_fattr(struct cifs_fattr *fattr, struct cifs_sb_info *cifs_sb); extern void cifs_dir_info_to_fattr(struct cifs_fattr *, FILE_DIRECTORY_INFO *, struct cifs_sb_info *); -extern int cifs_fattr_to_inode(struct inode *inode, struct cifs_fattr *fattr); +extern int cifs_fattr_to_inode(struct inode *inode, struct cifs_fattr *fattr, + bool from_readdir); extern struct inode *cifs_iget(struct super_block *sb, struct cifs_fattr *fattr); int cifs_get_inode_info(struct inode **inode, const char *full_path, struct cifs_open_info_data *data, struct super_block *sb, int xid, const struct cifs_fid *fid); -bool cifs_reparse_point_to_fattr(struct cifs_sb_info *cifs_sb, - struct cifs_fattr *fattr, - struct cifs_open_info_data *data); - extern int smb311_posix_get_inode_info(struct inode **inode, const char *full_path, struct cifs_open_info_data *data, @@ -296,6 +294,10 @@ extern void cifs_close_all_deferred_files(struct cifs_tcon *cifs_tcon); extern void cifs_close_deferred_file_under_dentry(struct cifs_tcon *cifs_tcon, const char *path); + +extern void cifs_mark_open_handles_for_deleted_file(struct inode *inode, + const char *path); + extern struct TCP_Server_Info * cifs_get_tcp_session(struct smb3_fs_context *ctx, struct TCP_Server_Info *primary_server); @@ -402,7 +404,8 @@ extern int CIFSSMBSetFileDisposition(const unsigned int xid, __u32 pid_of_opener); extern int CIFSSMBSetEOF(const unsigned int xid, struct cifs_tcon *tcon, const char *file_name, __u64 size, - struct cifs_sb_info *cifs_sb, bool set_allocation); + struct cifs_sb_info *cifs_sb, bool set_allocation, + struct dentry *dentry); extern int CIFSSMBSetFileSize(const unsigned int xid, struct cifs_tcon *tcon, struct cifsFileInfo *cfile, __u64 size, bool set_allocation); @@ -438,7 +441,8 @@ extern int CIFSPOSIXDelFile(const unsigned int xid, struct cifs_tcon *tcon, const struct nls_table *nls_codepage, int remap_special_chars); extern int CIFSSMBDelFile(const unsigned int xid, struct cifs_tcon *tcon, - const char *name, struct cifs_sb_info *cifs_sb); + const char *name, struct cifs_sb_info *cifs_sb, + struct dentry *dentry); int CIFSSMBRename(const unsigned int xid, struct cifs_tcon *tcon, struct dentry *source_dentry, const char *from_name, const char *to_name, diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c index 5eb83bafc7fd..5aee55551573 100644 --- a/fs/smb/client/cifssmb.c +++ b/fs/smb/client/cifssmb.c @@ -738,7 +738,7 @@ PsxDelete: int CIFSSMBDelFile(const unsigned int xid, struct cifs_tcon *tcon, const char *name, - struct cifs_sb_info *cifs_sb) + struct cifs_sb_info *cifs_sb, struct dentry *dentry) { DELETE_FILE_REQ *pSMB = NULL; DELETE_FILE_RSP *pSMBr = NULL; @@ -4993,7 +4993,7 @@ QFSPosixRetry: int CIFSSMBSetEOF(const unsigned int xid, struct cifs_tcon *tcon, const char *file_name, __u64 size, struct cifs_sb_info *cifs_sb, - bool set_allocation) + bool set_allocation, struct dentry *dentry) { struct smb_com_transaction2_spi_req *pSMB = NULL; struct smb_com_transaction2_spi_rsp *pSMBr = NULL; diff --git a/fs/smb/client/connect.c b/fs/smb/client/connect.c index ac9595504f4b..86ae578904a2 100644 --- a/fs/smb/client/connect.c +++ b/fs/smb/client/connect.c @@ -1736,7 +1736,7 @@ cifs_get_tcp_session(struct smb3_fs_context *ctx, tcp_ses->channel_sequence_num = 0; /* only tracked for primary channel */ tcp_ses->reconnect_instance = 1; tcp_ses->lstrp = jiffies; - tcp_ses->compress_algorithm = cpu_to_le16(ctx->compression); + tcp_ses->compression.requested = ctx->compress; spin_lock_init(&tcp_ses->req_lock); spin_lock_init(&tcp_ses->srv_lock); spin_lock_init(&tcp_ses->mid_lock); @@ -2803,6 +2803,8 @@ compare_mount_options(struct super_block *sb, struct cifs_mnt_data *mnt_data) return 0; if (old->ctx->closetimeo != new->ctx->closetimeo) return 0; + if (old->ctx->reparse_type != new->ctx->reparse_type) + return 0; return 1; } diff --git a/fs/smb/client/file.c b/fs/smb/client/file.c index c3b8e7091a4d..ec25d3c3e1ee 100644 --- a/fs/smb/client/file.c +++ b/fs/smb/client/file.c @@ -329,7 +329,7 @@ int cifs_posix_open(const char *full_path, struct inode **pinode, } } else { cifs_revalidate_mapping(*pinode); - rc = cifs_fattr_to_inode(*pinode, &fattr); + rc = cifs_fattr_to_inode(*pinode, &fattr, false); } posix_open_ret: @@ -486,6 +486,7 @@ struct cifsFileInfo *cifs_new_fileinfo(struct cifs_fid *fid, struct file *file, cfile->uid = current_fsuid(); cfile->dentry = dget(dentry); cfile->f_flags = file->f_flags; + cfile->status_file_deleted = false; cfile->invalidHandle = false; cfile->deferred_close_scheduled = false; cfile->tlink = cifs_get_tlink(tlink); @@ -1088,7 +1089,7 @@ int cifs_close(struct inode *inode, struct file *file) if ((cifs_sb->ctx->closetimeo && cinode->oplock == CIFS_CACHE_RHW_FLG) && cinode->lease_granted && !test_bit(CIFS_INO_CLOSE_ON_LOCK, &cinode->flags) && - dclose) { + dclose && !(cfile->status_file_deleted)) { if (test_and_clear_bit(CIFS_INO_MODIFIED_ATTR, &cinode->flags)) { inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); @@ -2628,20 +2629,20 @@ static int cifs_partialpagewrite(struct page *page, unsigned from, unsigned to) * dirty pages if possible, but don't sleep while doing so. */ static void cifs_extend_writeback(struct address_space *mapping, + struct xa_state *xas, long *_count, loff_t start, int max_pages, - size_t max_len, - unsigned int *_len) + loff_t max_len, + size_t *_len) { struct folio_batch batch; struct folio *folio; - unsigned int psize, nr_pages; - size_t len = *_len; - pgoff_t index = (start + len) / PAGE_SIZE; + unsigned int nr_pages; + pgoff_t index = (start + *_len) / PAGE_SIZE; + size_t len; bool stop = true; unsigned int i; - XA_STATE(xas, &mapping->i_pages, index); folio_batch_init(&batch); @@ -2652,54 +2653,64 @@ static void cifs_extend_writeback(struct address_space *mapping, */ rcu_read_lock(); - xas_for_each(&xas, folio, ULONG_MAX) { + xas_for_each(xas, folio, ULONG_MAX) { stop = true; - if (xas_retry(&xas, folio)) + if (xas_retry(xas, folio)) continue; if (xa_is_value(folio)) break; - if (folio->index != index) + if (folio->index != index) { + xas_reset(xas); break; + } + if (!folio_try_get_rcu(folio)) { - xas_reset(&xas); + xas_reset(xas); continue; } nr_pages = folio_nr_pages(folio); - if (nr_pages > max_pages) + if (nr_pages > max_pages) { + xas_reset(xas); break; + } /* Has the page moved or been split? */ - if (unlikely(folio != xas_reload(&xas))) { + if (unlikely(folio != xas_reload(xas))) { folio_put(folio); + xas_reset(xas); break; } if (!folio_trylock(folio)) { folio_put(folio); + xas_reset(xas); break; } - if (!folio_test_dirty(folio) || folio_test_writeback(folio)) { + if (!folio_test_dirty(folio) || + folio_test_writeback(folio)) { folio_unlock(folio); folio_put(folio); + xas_reset(xas); break; } max_pages -= nr_pages; - psize = folio_size(folio); - len += psize; + len = folio_size(folio); stop = false; - if (max_pages <= 0 || len >= max_len || *_count <= 0) - stop = true; index += nr_pages; + *_count -= nr_pages; + *_len += len; + if (max_pages <= 0 || *_len >= max_len || *_count <= 0) + stop = true; + if (!folio_batch_add(&batch, folio)) break; if (stop) break; } - if (!stop) - xas_pause(&xas); + xas_pause(xas); rcu_read_unlock(); /* Now, if we obtained any pages, we can shift them to being @@ -2716,16 +2727,12 @@ static void cifs_extend_writeback(struct address_space *mapping, if (!folio_clear_dirty_for_io(folio)) WARN_ON(1); folio_start_writeback(folio); - - *_count -= folio_nr_pages(folio); folio_unlock(folio); } folio_batch_release(&batch); cond_resched(); } while (!stop); - - *_len = len; } /* @@ -2733,8 +2740,10 @@ static void cifs_extend_writeback(struct address_space *mapping, */ static ssize_t cifs_write_back_from_locked_folio(struct address_space *mapping, struct writeback_control *wbc, + struct xa_state *xas, struct folio *folio, - loff_t start, loff_t end) + unsigned long long start, + unsigned long long end) { struct inode *inode = mapping->host; struct TCP_Server_Info *server; @@ -2743,17 +2752,18 @@ static ssize_t cifs_write_back_from_locked_folio(struct address_space *mapping, struct cifs_credits credits_on_stack; struct cifs_credits *credits = &credits_on_stack; struct cifsFileInfo *cfile = NULL; - unsigned int xid, wsize, len; - loff_t i_size = i_size_read(inode); - size_t max_len; + unsigned long long i_size = i_size_read(inode), max_len; + unsigned int xid, wsize; + size_t len = folio_size(folio); long count = wbc->nr_to_write; int rc; /* The folio should be locked, dirty and not undergoing writeback. */ + if (!folio_clear_dirty_for_io(folio)) + WARN_ON_ONCE(1); folio_start_writeback(folio); count -= folio_nr_pages(folio); - len = folio_size(folio); xid = get_xid(); server = cifs_pick_channel(cifs_sb_master_tcon(cifs_sb)->ses); @@ -2783,9 +2793,10 @@ static ssize_t cifs_write_back_from_locked_folio(struct address_space *mapping, wdata->server = server; cfile = NULL; - /* Find all consecutive lockable dirty pages, stopping when we find a - * page that is not immediately lockable, is not dirty or is missing, - * or we reach the end of the range. + /* Find all consecutive lockable dirty pages that have contiguous + * written regions, stopping when we find a page that is not + * immediately lockable, is not dirty or is missing, or we reach the + * end of the range. */ if (start < i_size) { /* Trim the write to the EOF; the extra data is ignored. Also @@ -2805,19 +2816,18 @@ static ssize_t cifs_write_back_from_locked_folio(struct address_space *mapping, max_pages -= folio_nr_pages(folio); if (max_pages > 0) - cifs_extend_writeback(mapping, &count, start, + cifs_extend_writeback(mapping, xas, &count, start, max_pages, max_len, &len); } - len = min_t(loff_t, len, max_len); } - - wdata->bytes = len; + len = min_t(unsigned long long, len, i_size - start); /* We now have a contiguous set of dirty pages, each with writeback * set; the first page is still locked at this point, but all the rest * have been unlocked. */ folio_unlock(folio); + wdata->bytes = len; if (start < i_size) { iov_iter_xarray(&wdata->iter, ITER_SOURCE, &mapping->i_pages, @@ -2868,102 +2878,118 @@ err_xid: /* * write a region of pages back to the server */ -static int cifs_writepages_region(struct address_space *mapping, - struct writeback_control *wbc, - loff_t start, loff_t end, loff_t *_next) +static ssize_t cifs_writepages_begin(struct address_space *mapping, + struct writeback_control *wbc, + struct xa_state *xas, + unsigned long long *_start, + unsigned long long end) { - struct folio_batch fbatch; + struct folio *folio; + unsigned long long start = *_start; + ssize_t ret; int skips = 0; - folio_batch_init(&fbatch); - do { - int nr; - pgoff_t index = start / PAGE_SIZE; +search_again: + /* Find the first dirty page. */ + rcu_read_lock(); - nr = filemap_get_folios_tag(mapping, &index, end / PAGE_SIZE, - PAGECACHE_TAG_DIRTY, &fbatch); - if (!nr) + for (;;) { + folio = xas_find_marked(xas, end / PAGE_SIZE, PAGECACHE_TAG_DIRTY); + if (xas_retry(xas, folio) || xa_is_value(folio)) + continue; + if (!folio) break; - for (int i = 0; i < nr; i++) { - ssize_t ret; - struct folio *folio = fbatch.folios[i]; + if (!folio_try_get_rcu(folio)) { + xas_reset(xas); + continue; + } -redo_folio: - start = folio_pos(folio); /* May regress with THPs */ + if (unlikely(folio != xas_reload(xas))) { + folio_put(folio); + xas_reset(xas); + continue; + } - /* At this point we hold neither the i_pages lock nor the - * page lock: the page may be truncated or invalidated - * (changing page->mapping to NULL), or even swizzled - * back from swapper_space to tmpfs file mapping - */ - if (wbc->sync_mode != WB_SYNC_NONE) { - ret = folio_lock_killable(folio); - if (ret < 0) - goto write_error; - } else { - if (!folio_trylock(folio)) - goto skip_write; - } + xas_pause(xas); + break; + } + rcu_read_unlock(); + if (!folio) + return 0; - if (folio->mapping != mapping || - !folio_test_dirty(folio)) { - start += folio_size(folio); - folio_unlock(folio); - continue; - } + start = folio_pos(folio); /* May regress with THPs */ - if (folio_test_writeback(folio) || - folio_test_fscache(folio)) { - folio_unlock(folio); - if (wbc->sync_mode == WB_SYNC_NONE) - goto skip_write; + /* At this point we hold neither the i_pages lock nor the page lock: + * the page may be truncated or invalidated (changing page->mapping to + * NULL), or even swizzled back from swapper_space to tmpfs file + * mapping + */ +lock_again: + if (wbc->sync_mode != WB_SYNC_NONE) { + ret = folio_lock_killable(folio); + if (ret < 0) + return ret; + } else { + if (!folio_trylock(folio)) + goto search_again; + } + + if (folio->mapping != mapping || + !folio_test_dirty(folio)) { + start += folio_size(folio); + folio_unlock(folio); + goto search_again; + } - folio_wait_writeback(folio); + if (folio_test_writeback(folio) || + folio_test_fscache(folio)) { + folio_unlock(folio); + if (wbc->sync_mode != WB_SYNC_NONE) { + folio_wait_writeback(folio); #ifdef CONFIG_CIFS_FSCACHE - folio_wait_fscache(folio); + folio_wait_fscache(folio); #endif - goto redo_folio; - } - - if (!folio_clear_dirty_for_io(folio)) - /* We hold the page lock - it should've been dirty. */ - WARN_ON(1); - - ret = cifs_write_back_from_locked_folio(mapping, wbc, folio, start, end); - if (ret < 0) - goto write_error; - - start += ret; - continue; - -write_error: - folio_batch_release(&fbatch); - *_next = start; - return ret; + goto lock_again; + } -skip_write: - /* - * Too many skipped writes, or need to reschedule? - * Treat it as a write error without an error code. - */ + start += folio_size(folio); + if (wbc->sync_mode == WB_SYNC_NONE) { if (skips >= 5 || need_resched()) { ret = 0; - goto write_error; + goto out; } - - /* Otherwise, just skip that folio and go on to the next */ skips++; - start += folio_size(folio); - continue; } + goto search_again; + } - folio_batch_release(&fbatch); - cond_resched(); - } while (wbc->nr_to_write > 0); + ret = cifs_write_back_from_locked_folio(mapping, wbc, xas, folio, start, end); +out: + if (ret > 0) + *_start = start + ret; + return ret; +} - *_next = start; - return 0; +/* + * Write a region of pages back to the server + */ +static int cifs_writepages_region(struct address_space *mapping, + struct writeback_control *wbc, + unsigned long long *_start, + unsigned long long end) +{ + ssize_t ret; + + XA_STATE(xas, &mapping->i_pages, *_start / PAGE_SIZE); + + do { + ret = cifs_writepages_begin(mapping, wbc, &xas, _start, end); + if (ret > 0 && wbc->nr_to_write > 0) + cond_resched(); + } while (ret > 0 && wbc->nr_to_write > 0); + + return ret > 0 ? 0 : ret; } /* @@ -2972,7 +2998,7 @@ skip_write: static int cifs_writepages(struct address_space *mapping, struct writeback_control *wbc) { - loff_t start, next; + loff_t start, end; int ret; /* We have to be careful as we can end up racing with setattr() @@ -2980,28 +3006,34 @@ static int cifs_writepages(struct address_space *mapping, * to prevent it. */ - if (wbc->range_cyclic) { + if (wbc->range_cyclic && mapping->writeback_index) { start = mapping->writeback_index * PAGE_SIZE; - ret = cifs_writepages_region(mapping, wbc, start, LLONG_MAX, &next); - if (ret == 0) { - mapping->writeback_index = next / PAGE_SIZE; - if (start > 0 && wbc->nr_to_write > 0) { - ret = cifs_writepages_region(mapping, wbc, 0, - start, &next); - if (ret == 0) - mapping->writeback_index = - next / PAGE_SIZE; - } + ret = cifs_writepages_region(mapping, wbc, &start, LLONG_MAX); + if (ret < 0) + goto out; + + if (wbc->nr_to_write <= 0) { + mapping->writeback_index = start / PAGE_SIZE; + goto out; } + + start = 0; + end = mapping->writeback_index * PAGE_SIZE; + mapping->writeback_index = 0; + ret = cifs_writepages_region(mapping, wbc, &start, end); + if (ret == 0) + mapping->writeback_index = start / PAGE_SIZE; } else if (wbc->range_start == 0 && wbc->range_end == LLONG_MAX) { - ret = cifs_writepages_region(mapping, wbc, 0, LLONG_MAX, &next); + start = 0; + ret = cifs_writepages_region(mapping, wbc, &start, LLONG_MAX); if (wbc->nr_to_write > 0 && ret == 0) - mapping->writeback_index = next / PAGE_SIZE; + mapping->writeback_index = start / PAGE_SIZE; } else { - ret = cifs_writepages_region(mapping, wbc, - wbc->range_start, wbc->range_end, &next); + start = wbc->range_start; + ret = cifs_writepages_region(mapping, wbc, &start, wbc->range_end); } +out: return ret; } @@ -3098,8 +3130,15 @@ static int cifs_write_end(struct file *file, struct address_space *mapping, if (rc > 0) { spin_lock(&inode->i_lock); if (pos > inode->i_size) { + loff_t additional_blocks = (512 - 1 + copied) >> 9; + i_size_write(inode, pos); - inode->i_blocks = (512 - 1 + pos) >> 9; + /* + * Estimate new allocation size based on the amount written. + * This will be updated from server on close (and on queryinfo) + */ + inode->i_blocks = min_t(blkcnt_t, (512 - 1 + pos) >> 9, + inode->i_blocks + additional_blocks); } spin_unlock(&inode->i_lock); } @@ -4742,12 +4781,14 @@ static int is_inode_writable(struct cifsInodeInfo *cifs_inode) refreshing the inode only on increases in the file size but this is tricky to d |
