Skip to content

Commit 68d2e2c

Browse files
hac-vsmfrench
authored andcommitted
smb: client: batch SRV_COPYCHUNK entries to cut round trips
smb2_copychunk_range() used to send a single SRV_COPYCHUNK per SRV_COPYCHUNK_COPY IOCTL. Implement variable Chunks[] array in struct copychunk_ioctl and fill it with struct copychunk (MS-SMB2 2.2.31.1.1), bounded by server-advertised limits. This reduces the number of IOCTL requests for large copies. While we are at it, rename a couple variables to follow the terminology used in the specification. Signed-off-by: Henrique Carvalho <[email protected]> Signed-off-by: Steve French <[email protected]>
1 parent 1643cd5 commit 68d2e2c

3 files changed

Lines changed: 207 additions & 117 deletions

File tree

fs/smb/client/smb2ops.c

Lines changed: 196 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -1803,140 +1803,226 @@ smb2_ioctl_query_info(const unsigned int xid,
18031803
return rc;
18041804
}
18051805

1806+
/**
1807+
* calc_chunk_count - calculates the number chunks to be filled in the Chunks[]
1808+
* array of struct copychunk_ioctl
1809+
*
1810+
* @tcon: destination file tcon
1811+
* @bytes_left: how many bytes are left to copy
1812+
*
1813+
* Return: maximum number of chunks with which Chunks[] can be filled.
1814+
*/
1815+
static inline u32
1816+
calc_chunk_count(struct cifs_tcon *tcon, u64 bytes_left)
1817+
{
1818+
u32 max_chunks = READ_ONCE(tcon->max_chunks);
1819+
u32 max_bytes_copy = READ_ONCE(tcon->max_bytes_copy);
1820+
u32 max_bytes_chunk = READ_ONCE(tcon->max_bytes_chunk);
1821+
u64 need;
1822+
u32 allowed;
1823+
1824+
if (!max_bytes_chunk || !max_bytes_copy || !max_chunks)
1825+
return 0;
1826+
1827+
/* chunks needed for the remaining bytes */
1828+
need = DIV_ROUND_UP_ULL(bytes_left, max_bytes_chunk);
1829+
/* chunks allowed per cc request */
1830+
allowed = DIV_ROUND_UP(max_bytes_copy, max_bytes_chunk);
1831+
1832+
return (u32)umin(need, umin(max_chunks, allowed));
1833+
}
1834+
1835+
/**
1836+
* smb2_copychunk_range - server-side copy of data range
1837+
*
1838+
* @xid: transaction id
1839+
* @src_file: source file
1840+
* @dst_file: destination file
1841+
* @src_off: source file byte offset
1842+
* @len: number of bytes to copy
1843+
* @dst_off: destination file byte offset
1844+
*
1845+
* Obtains a resume key for @src_file and issues FSCTL_SRV_COPYCHUNK_WRITE
1846+
* IOCTLs, splitting the request into chunks limited by tcon->max_*.
1847+
*
1848+
* Return: @len on success; negative errno on failure.
1849+
*/
18061850
static ssize_t
18071851
smb2_copychunk_range(const unsigned int xid,
1808-
struct cifsFileInfo *srcfile,
1809-
struct cifsFileInfo *trgtfile, u64 src_off,
1810-
u64 len, u64 dest_off)
1852+
struct cifsFileInfo *src_file,
1853+
struct cifsFileInfo *dst_file,
1854+
u64 src_off,
1855+
u64 len,
1856+
u64 dst_off)
18111857
{
1812-
int rc;
1813-
unsigned int ret_data_len;
1814-
struct copychunk_ioctl *pcchunk;
1815-
struct copychunk_ioctl_rsp *retbuf = NULL;
1858+
int rc = 0;
1859+
unsigned int ret_data_len = 0;
1860+
struct copychunk_ioctl *cc_req = NULL;
1861+
struct copychunk_ioctl_rsp *cc_rsp = NULL;
18161862
struct cifs_tcon *tcon;
1817-
int chunks_copied = 0;
1818-
bool chunk_sizes_updated = false;
1819-
ssize_t bytes_written, total_bytes_written = 0;
1863+
struct copychunk *chunk;
1864+
u32 chunks, chunk_count, chunk_bytes;
1865+
u32 copy_bytes, copy_bytes_left;
1866+
u32 chunks_written, bytes_written;
1867+
u64 total_bytes_left = len;
1868+
u64 src_off_prev, dst_off_prev;
1869+
u32 retries = 0;
1870+
1871+
tcon = tlink_tcon(dst_file->tlink);
1872+
1873+
trace_smb3_copychunk_enter(xid, src_file->fid.volatile_fid,
1874+
dst_file->fid.volatile_fid, tcon->tid,
1875+
tcon->ses->Suid, src_off, dst_off, len);
1876+
1877+
retry:
1878+
chunk_count = calc_chunk_count(tcon, total_bytes_left);
1879+
if (!chunk_count) {
1880+
rc = -EOPNOTSUPP;
1881+
goto out;
1882+
}
18201883

1821-
pcchunk = kmalloc(sizeof(struct copychunk_ioctl), GFP_KERNEL);
1822-
if (pcchunk == NULL)
1823-
return -ENOMEM;
1884+
cc_req = kzalloc(struct_size(cc_req, Chunks, chunk_count), GFP_KERNEL);
1885+
if (!cc_req) {
1886+
rc = -ENOMEM;
1887+
goto out;
1888+
}
18241889

1825-
cifs_dbg(FYI, "%s: about to call request res key\n", __func__);
18261890
/* Request a key from the server to identify the source of the copy */
1827-
rc = SMB2_request_res_key(xid, tlink_tcon(srcfile->tlink),
1828-
srcfile->fid.persistent_fid,
1829-
srcfile->fid.volatile_fid, pcchunk);
1891+
rc = SMB2_request_res_key(xid,
1892+
tlink_tcon(src_file->tlink),
1893+
src_file->fid.persistent_fid,
1894+
src_file->fid.volatile_fid,
1895+
cc_req);
18301896

1831-
/* Note: request_res_key sets res_key null only if rc !=0 */
1897+
/* Note: request_res_key sets res_key null only if rc != 0 */
18321898
if (rc)
1833-
goto cchunk_out;
1899+
goto out;
18341900

1835-
/* For now array only one chunk long, will make more flexible later */
1836-
pcchunk->ChunkCount = cpu_to_le32(1);
1837-
pcchunk->Reserved = 0;
1838-
pcchunk->Reserved2 = 0;
1901+
while (total_bytes_left > 0) {
18391902

1840-
tcon = tlink_tcon(trgtfile->tlink);
1903+
/* Store previous offsets to allow rewind */
1904+
src_off_prev = src_off;
1905+
dst_off_prev = dst_off;
18411906

1842-
trace_smb3_copychunk_enter(xid, srcfile->fid.volatile_fid,
1843-
trgtfile->fid.volatile_fid, tcon->tid,
1844-
tcon->ses->Suid, src_off, dest_off, len);
1907+
chunks = 0;
1908+
copy_bytes = 0;
1909+
copy_bytes_left = umin(total_bytes_left, tcon->max_bytes_copy);
1910+
while (copy_bytes_left > 0 && chunks < chunk_count) {
1911+
chunk = &cc_req->Chunks[chunks++];
18451912

1846-
while (len > 0) {
1847-
pcchunk->SourceOffset = cpu_to_le64(src_off);
1848-
pcchunk->TargetOffset = cpu_to_le64(dest_off);
1849-
pcchunk->Length =
1850-
cpu_to_le32(min_t(u64, len, tcon->max_bytes_chunk));
1913+
chunk->SourceOffset = cpu_to_le64(src_off);
1914+
chunk->TargetOffset = cpu_to_le64(dst_off);
1915+
1916+
chunk_bytes = umin(copy_bytes_left, tcon->max_bytes_chunk);
1917+
1918+
chunk->Length = cpu_to_le32(chunk_bytes);
1919+
/* Buffer is zeroed, no need to set chunk->Reserved = 0 */
1920+
1921+
src_off += chunk_bytes;
1922+
dst_off += chunk_bytes;
1923+
1924+
copy_bytes_left -= chunk_bytes;
1925+
copy_bytes += chunk_bytes;
1926+
}
1927+
1928+
cc_req->ChunkCount = cpu_to_le32(chunks);
1929+
/* Buffer is zeroed, no need to set cc_req->Reserved = 0 */
18511930

18521931
/* Request server copy to target from src identified by key */
1853-
kfree(retbuf);
1854-
retbuf = NULL;
1855-
rc = SMB2_ioctl(xid, tcon, trgtfile->fid.persistent_fid,
1856-
trgtfile->fid.volatile_fid, FSCTL_SRV_COPYCHUNK_WRITE,
1857-
(char *)pcchunk, sizeof(struct copychunk_ioctl),
1858-
CIFSMaxBufSize, (char **)&retbuf, &ret_data_len);
1932+
kfree(cc_rsp);
1933+
cc_rsp = NULL;
1934+
rc = SMB2_ioctl(xid, tcon, dst_file->fid.persistent_fid,
1935+
dst_file->fid.volatile_fid, FSCTL_SRV_COPYCHUNK_WRITE,
1936+
(char *)cc_req, struct_size(cc_req, Chunks, chunks),
1937+
CIFSMaxBufSize, (char **)&cc_rsp, &ret_data_len);
1938+
1939+
if (rc && rc != -EINVAL)
1940+
goto out;
1941+
1942+
if (unlikely(ret_data_len != sizeof(*cc_rsp))) {
1943+
cifs_tcon_dbg(VFS, "Copychunk invalid response: size %u/%zu\n",
1944+
ret_data_len, sizeof(*cc_rsp));
1945+
rc = -EIO;
1946+
goto out;
1947+
}
1948+
1949+
bytes_written = le32_to_cpu(cc_rsp->TotalBytesWritten);
1950+
chunks_written = le32_to_cpu(cc_rsp->ChunksWritten);
1951+
chunk_bytes = le32_to_cpu(cc_rsp->ChunkBytesWritten);
1952+
18591953
if (rc == 0) {
1860-
if (ret_data_len !=
1861-
sizeof(struct copychunk_ioctl_rsp)) {
1862-
cifs_tcon_dbg(VFS, "Invalid cchunk response size\n");
1863-
rc = -EIO;
1864-
goto cchunk_out;
1865-
}
1866-
if (retbuf->TotalBytesWritten == 0) {
1867-
cifs_dbg(FYI, "no bytes copied\n");
1868-
rc = -EIO;
1869-
goto cchunk_out;
1870-
}
1871-
/*
1872-
* Check if server claimed to write more than we asked
1873-
*/
1874-
if (le32_to_cpu(retbuf->TotalBytesWritten) >
1875-
le32_to_cpu(pcchunk->Length)) {
1876-
cifs_tcon_dbg(VFS, "Invalid copy chunk response\n");
1954+
/* Check if server claimed to write more than we asked */
1955+
if (unlikely(!bytes_written || bytes_written > copy_bytes ||
1956+
!chunks_written || chunks_written > chunks)) {
1957+
cifs_tcon_dbg(VFS, "Copychunk invalid response: bytes written %u/%u, chunks written %u/%u\n",
1958+
bytes_written, copy_bytes, chunks_written, chunks);
18771959
rc = -EIO;
1878-
goto cchunk_out;
1960+
goto out;
18791961
}
1880-
if (le32_to_cpu(retbuf->ChunksWritten) != 1) {
1881-
cifs_tcon_dbg(VFS, "Invalid num chunks written\n");
1882-
rc = -EIO;
1883-
goto cchunk_out;
1962+
1963+
/* Partial write: rewind */
1964+
if (bytes_written < copy_bytes) {
1965+
u32 delta = copy_bytes - bytes_written;
1966+
1967+
src_off -= delta;
1968+
dst_off -= delta;
18841969
}
1885-
chunks_copied++;
1886-
1887-
bytes_written = le32_to_cpu(retbuf->TotalBytesWritten);
1888-
src_off += bytes_written;
1889-
dest_off += bytes_written;
1890-
len -= bytes_written;
1891-
total_bytes_written += bytes_written;
1892-
1893-
cifs_dbg(FYI, "Chunks %d PartialChunk %d Total %zu\n",
1894-
le32_to_cpu(retbuf->ChunksWritten),
1895-
le32_to_cpu(retbuf->ChunkBytesWritten),
1896-
bytes_written);
1897-
trace_smb3_copychunk_done(xid, srcfile->fid.volatile_fid,
1898-
trgtfile->fid.volatile_fid, tcon->tid,
1899-
tcon->ses->Suid, src_off, dest_off, len);
1900-
} else if (rc == -EINVAL) {
1901-
if (ret_data_len != sizeof(struct copychunk_ioctl_rsp))
1902-
goto cchunk_out;
1903-
1904-
cifs_dbg(FYI, "MaxChunks %d BytesChunk %d MaxCopy %d\n",
1905-
le32_to_cpu(retbuf->ChunksWritten),
1906-
le32_to_cpu(retbuf->ChunkBytesWritten),
1907-
le32_to_cpu(retbuf->TotalBytesWritten));
19081970

1909-
/*
1910-
* Check if this is the first request using these sizes,
1911-
* (ie check if copy succeed once with original sizes
1912-
* and check if the server gave us different sizes after
1913-
* we already updated max sizes on previous request).
1914-
* if not then why is the server returning an error now
1915-
*/
1916-
if ((chunks_copied != 0) || chunk_sizes_updated)
1917-
goto cchunk_out;
1918-
1919-
/* Check that server is not asking us to grow size */
1920-
if (le32_to_cpu(retbuf->ChunkBytesWritten) <
1921-
tcon->max_bytes_chunk)
1922-
tcon->max_bytes_chunk =
1923-
le32_to_cpu(retbuf->ChunkBytesWritten);
1924-
else
1925-
goto cchunk_out; /* server gave us bogus size */
1971+
total_bytes_left -= bytes_written;
1972+
continue;
1973+
}
19261974

1927-
/* No need to change MaxChunks since already set to 1 */
1928-
chunk_sizes_updated = true;
1929-
} else
1930-
goto cchunk_out;
1975+
/*
1976+
* Check if server is not asking us to reduce size.
1977+
*
1978+
* Note: As per MS-SMB2 2.2.32.1, the values returned
1979+
* in cc_rsp are not strictly lower than what existed
1980+
* before.
1981+
*/
1982+
if (bytes_written < tcon->max_bytes_copy) {
1983+
cifs_tcon_dbg(FYI, "Copychunk MaxBytesCopy updated: %u -> %u\n",
1984+
tcon->max_bytes_copy, bytes_written);
1985+
tcon->max_bytes_copy = bytes_written;
1986+
}
1987+
1988+
if (chunks_written < tcon->max_chunks) {
1989+
cifs_tcon_dbg(FYI, "Copychunk MaxChunks updated: %u -> %u\n",
1990+
tcon->max_chunks, chunks_written);
1991+
tcon->max_chunks = chunks_written;
1992+
}
1993+
1994+
if (chunk_bytes < tcon->max_bytes_chunk) {
1995+
cifs_tcon_dbg(FYI, "Copychunk MaxBytesChunk updated: %u -> %u\n",
1996+
tcon->max_bytes_chunk, chunk_bytes);
1997+
tcon->max_bytes_chunk = chunk_bytes;
1998+
}
1999+
2000+
/* reset to last offsets */
2001+
if (retries++ < 2) {
2002+
src_off = src_off_prev;
2003+
dst_off = dst_off_prev;
2004+
kfree(cc_req);
2005+
cc_req = NULL;
2006+
goto retry;
2007+
}
2008+
2009+
break;
19312010
}
19322011

1933-
cchunk_out:
1934-
kfree(pcchunk);
1935-
kfree(retbuf);
1936-
if (rc)
2012+
out:
2013+
kfree(cc_req);
2014+
kfree(cc_rsp);
2015+
if (rc) {
2016+
trace_smb3_copychunk_err(xid, src_file->fid.volatile_fid,
2017+
dst_file->fid.volatile_fid, tcon->tid,
2018+
tcon->ses->Suid, src_off, dst_off, len, rc);
19372019
return rc;
1938-
else
1939-
return total_bytes_written;
2020+
} else {
2021+
trace_smb3_copychunk_done(xid, src_file->fid.volatile_fid,
2022+
dst_file->fid.volatile_fid, tcon->tid,
2023+
tcon->ses->Suid, src_off, dst_off, len);
2024+
return len;
2025+
}
19402026
}
19412027

19422028
static int

fs/smb/client/smb2pdu.h

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -201,16 +201,20 @@ struct resume_key_req {
201201
char Context[]; /* ignored, Windows sets to 4 bytes of zero */
202202
} __packed;
203203

204+
205+
struct copychunk {
206+
__le64 SourceOffset;
207+
__le64 TargetOffset;
208+
__le32 Length;
209+
__le32 Reserved;
210+
} __packed;
211+
204212
/* this goes in the ioctl buffer when doing a copychunk request */
205213
struct copychunk_ioctl {
206214
char SourceKey[COPY_CHUNK_RES_KEY_SIZE];
207-
__le32 ChunkCount; /* we are only sending 1 */
215+
__le32 ChunkCount;
208216
__le32 Reserved;
209-
/* array will only be one chunk long for us */
210-
__le64 SourceOffset;
211-
__le64 TargetOffset;
212-
__le32 Length; /* how many bytes to copy */
213-
__u32 Reserved2;
217+
struct copychunk Chunks[];
214218
} __packed;
215219

216220
struct copychunk_ioctl_rsp {

fs/smb/client/trace.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ DEFINE_EVENT(smb3_copy_range_err_class, smb3_##name, \
266266
TP_ARGS(xid, src_fid, target_fid, tid, sesid, src_offset, target_offset, len, rc))
267267

268268
DEFINE_SMB3_COPY_RANGE_ERR_EVENT(clone_err);
269-
/* TODO: Add SMB3_COPY_RANGE_ERR_EVENT(copychunk_err) */
269+
DEFINE_SMB3_COPY_RANGE_ERR_EVENT(copychunk_err);
270270

271271
DECLARE_EVENT_CLASS(smb3_copy_range_done_class,
272272
TP_PROTO(unsigned int xid,

0 commit comments

Comments
 (0)