ksmbd: persist FSCTL_SET_SPARSE state

Advertise FILE_SUPPORTS_SPARSE_FILES so clients can use FSCTL_SET_SPARSE.
Do not mark regular files sparse just because sparse support is advertised;
FILE_ATTRIBUTE_SPARSE_FILE should reflect the state set by
FSCTL_SET_SPARSE.

Persist the sparse attribute in the DOS attribute xattr regardless of
the store dos attributes setting.  Restore the sparse and compressed bits
from that xattr when only those emulated attributes need to be preserved.

Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
This commit is contained in:
Namjae Jeon 2026-07-08 22:12:20 +09:00
parent 962b9df472
commit 159e727f76

View File

@ -948,9 +948,6 @@ static int smb2_get_dos_mode(struct kstat *stat, int attribute)
} else {
attr = (attribute & 0x00005137) | FILE_ATTRIBUTE_ARCHIVE;
attr &= ~(FILE_ATTRIBUTE_DIRECTORY);
if (S_ISREG(stat->mode) && (server_conf.share_fake_fscaps &
FILE_SUPPORTS_SPARSE_FILES))
attr |= FILE_ATTRIBUTE_SPARSE_FILE;
if (smb2_get_reparse_tag_special_file(stat->mode))
attr |= FILE_ATTRIBUTE_REPARSE_POINT;
@ -2998,24 +2995,18 @@ static void smb2_update_xattrs(struct ksmbd_tree_connect *tcon,
rc = ksmbd_vfs_get_dos_attrib_xattr(mnt_idmap(path->mnt),
path->dentry, &da);
if (rc > 0) {
/*
* Don't report a stale SPARSE bit (e.g. left over from a
* previous client, or from before the share was reconfigured)
* when the share isn't currently advertising sparse-file
* support. Time Machine sparsebundle band files rely on
* sparse status being accurate, since macOS decides whether
* to use FSCTL_SET_SPARSE based on it.
*/
if (!(server_conf.share_fake_fscaps & FILE_SUPPORTS_SPARSE_FILES))
da.attr &= ~FILE_ATTRIBUTE_SPARSE_FILE;
if (store_dos_attrs) {
fp->f_ci->m_fattr = cpu_to_le32(da.attr);
fp->create_time = da.create_time;
fp->itime = da.itime;
} else if (da.attr & FILE_ATTRIBUTE_COMPRESSED) {
fp->f_ci->m_fattr |= FILE_ATTRIBUTE_COMPRESSED_LE;
} else {
fp->f_ci->m_fattr &= ~FILE_ATTRIBUTE_COMPRESSED_LE;
fp->f_ci->m_fattr &=
~(FILE_ATTRIBUTE_COMPRESSED_LE |
FILE_ATTRIBUTE_SPARSE_FILE_LE);
fp->f_ci->m_fattr |=
cpu_to_le32(da.attr &
(FILE_ATTRIBUTE_COMPRESSED |
FILE_ATTRIBUTE_SPARSE_FILE));
}
}
}
@ -6314,6 +6305,7 @@ static int smb2_get_info_filesystem(struct ksmbd_work *work,
FILE_PERSISTENT_ACLS |
FILE_UNICODE_ON_DISK |
FILE_FILE_COMPRESSION |
FILE_SUPPORTS_SPARSE_FILES |
FILE_SUPPORTS_BLOCK_REFCOUNTING;
err = vfs_fileattr_get(path.dentry, &fa);
@ -9190,18 +9182,22 @@ static inline int fsctl_set_sparse(struct ksmbd_work *work, u64 id,
else
fp->f_ci->m_fattr &= ~FILE_ATTRIBUTE_SPARSE_FILE_LE;
if (fp->f_ci->m_fattr != old_fattr &&
test_share_config_flag(work->tcon->share_conf,
KSMBD_SHARE_FLAG_STORE_DOS_ATTRS)) {
if (fp->f_ci->m_fattr != old_fattr) {
const struct cred *saved_cred;
struct xattr_dos_attrib da;
struct xattr_dos_attrib da = {0};
ret = ksmbd_vfs_get_dos_attrib_xattr(idmap,
fp->filp->f_path.dentry, &da);
if (ret <= 0)
goto out;
if (ret <= 0) {
da.version = 4;
da.itime = fp->itime;
da.create_time = fp->create_time;
da.flags = XATTR_DOSINFO_CREATE_TIME |
XATTR_DOSINFO_ITIME;
}
da.attr = le32_to_cpu(fp->f_ci->m_fattr);
da.flags |= XATTR_DOSINFO_ATTRIB;
saved_cred = override_creds(fp->filp->f_cred);
ret = ksmbd_vfs_set_dos_attrib_xattr(idmap,
&fp->filp->f_path,