Skip to content

Commit 525ff49

Browse files
authored
Merge pull request RustPython#2311 from rodrigocam/master
Add socket sendfile function
2 parents 7b8066d + ad3922a commit 525ff49

File tree

7 files changed

+98
-4
lines changed

7 files changed

+98
-4
lines changed

Lib/shutil.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,14 +152,16 @@ def _fastcopy_sendfile(fsrc, fdst):
152152
err.filename = fsrc.name
153153
err.filename2 = fdst.name
154154

155-
if err.errno == errno.ENOTSOCK:
155+
# XXX RUSTPYTHON TODO: consistent OSError.errno
156+
if hasattr(err, "errno") and err.errno == errno.ENOTSOCK:
156157
# sendfile() on this platform (probably Linux < 2.6.33)
157158
# does not support copies between regular files (only
158159
# sockets).
159160
_USE_CP_SENDFILE = False
160161
raise _GiveupOnFastCopy(err)
161162

162-
if err.errno == errno.ENOSPC: # filesystem is full
163+
# XXX RUSTPYTHON TODO: consistent OSError.errno
164+
if hasattr(err, "errno") and err.errno == errno.ENOSPC: # filesystem is full
163165
raise err from None
164166

165167
# Give up on first call and if no data was copied.

Lib/test/test_os.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3067,6 +3067,7 @@ def handle_error(self):
30673067
raise
30683068

30693069

3070+
@unittest.skip("TODO: RUSTPYTHON (ValueError: invalid mode: 'xb')")
30703071
@unittest.skipUnless(hasattr(os, 'sendfile'), "test needs os.sendfile()")
30713072
class TestSendfile(unittest.TestCase):
30723073

Lib/test/test_shutil.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2366,6 +2366,8 @@ def test_non_regular_file_dst(self):
23662366
dst.seek(0)
23672367
self.assertEqual(dst.read(), self.FILEDATA)
23682368

2369+
# TODO: RUSTPYTHON
2370+
@unittest.expectedFailure
23692371
def test_exception_on_second_call(self):
23702372
def sendfile(*args, **kwargs):
23712373
if not flag:
@@ -2438,6 +2440,7 @@ def test_blocksize_arg(self):
24382440
blocksize = m.call_args[0][3]
24392441
self.assertEqual(blocksize, 2 ** 23)
24402442

2443+
@unittest.skip("TODO: RUSTPYTHON, unittest.mock")
24412444
def test_file2file_not_supported(self):
24422445
# Emulate a case where sendfile() only support file->socket
24432446
# fds. In such a case copyfile() is supposed to skip the

extra_tests/snippets/stdlib_os.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,19 @@
1717
assert_raises(FileNotFoundError,
1818
lambda: os.rename('DOES_NOT_EXIST', 'DOES_NOT_EXIST 2'))
1919

20+
if hasattr(os, "sendfile"):
21+
src_fd = os.open('README.md', os.O_RDONLY)
22+
dest_fd = os.open('destination.md', os.O_RDWR | os.O_CREAT)
23+
src_len = os.stat('README.md').st_size
24+
25+
bytes_sent = os.sendfile(dest_fd, src_fd, 0, src_len)
26+
assert src_len == bytes_sent
27+
28+
os.lseek(dest_fd, 0, 0)
29+
assert os.read(src_fd, src_len) == os.read(dest_fd, bytes_sent)
30+
os.close(src_fd)
31+
os.close(dest_fd)
32+
2033
try:
2134
os.open('DOES_NOT_EXIST', 0)
2235
except OSError as err:

extra_tests/snippets/stdlib_socket.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,14 @@
2323
assert recv_a == MESSAGE_A
2424
assert recv_b == MESSAGE_B
2525

26+
fd = open('README.md', 'rb')
27+
connector.sendfile(fd)
28+
recv_readme = connection.recv(os.stat('README.md').st_size)
29+
# need this because sendfile leaves the cursor at the end of the file
30+
fd.seek(0)
31+
assert recv_readme == fd.read()
32+
fd.close()
33+
2634
# fileno
2735
if os.name == "posix":
2836
connector_fd = connector.fileno()

vm/src/function.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -506,15 +506,17 @@ macro_rules! tuple_from_py_func_args {
506506
};
507507
}
508508

509-
// Implement `FromArgs` for up to 5-tuples, allowing built-in functions to bind
510-
// up to 5 top-level parameters (note that `Args`, `KwArgs`, nested tuples, etc.
509+
// Implement `FromArgs` for up to 7-tuples, allowing built-in functions to bind
510+
// up to 7 top-level parameters (note that `Args`, `KwArgs`, nested tuples, etc.
511511
// count as 1, so this should actually be more than enough).
512512
tuple_from_py_func_args!(A);
513513
tuple_from_py_func_args!(A, B);
514514
tuple_from_py_func_args!(A, B, C);
515515
tuple_from_py_func_args!(A, B, C, D);
516516
tuple_from_py_func_args!(A, B, C, D, E);
517517
tuple_from_py_func_args!(A, B, C, D, E, F);
518+
tuple_from_py_func_args!(A, B, C, D, E, F, G);
519+
tuple_from_py_func_args!(A, B, C, D, E, F, G, H);
518520

519521
/// A built-in Python function.
520522
pub type PyNativeFunc = Box<py_dyn_fn!(dyn Fn(&VirtualMachine, FuncArgs) -> PyResult)>;
@@ -640,6 +642,16 @@ into_py_native_func_tuple!((v1, T1), (v2, T2));
640642
into_py_native_func_tuple!((v1, T1), (v2, T2), (v3, T3));
641643
into_py_native_func_tuple!((v1, T1), (v2, T2), (v3, T3), (v4, T4));
642644
into_py_native_func_tuple!((v1, T1), (v2, T2), (v3, T3), (v4, T4), (v5, T5));
645+
into_py_native_func_tuple!((v1, T1), (v2, T2), (v3, T3), (v4, T4), (v5, T5), (v6, T6));
646+
into_py_native_func_tuple!(
647+
(v1, T1),
648+
(v2, T2),
649+
(v3, T3),
650+
(v4, T4),
651+
(v5, T5),
652+
(v6, T6),
653+
(v7, T7)
654+
);
643655

644656
/// Tests that the predicate is True on a single value, or if the value is a tuple a tuple, then
645657
/// test that any of the values contained within the tuples satisfies the predicate. Type parameter

vm/src/stdlib/os.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,61 @@ mod _os {
337337
Err(vm.new_os_error("os.open not implemented on this platform".to_owned()))
338338
}
339339

340+
#[cfg(any(target_os = "linux"))]
341+
#[pyfunction]
342+
fn sendfile(out_fd: i32, in_fd: i32, offset: i64, count: u64, vm: &VirtualMachine) -> PyResult {
343+
let mut file_offset = offset;
344+
345+
let res =
346+
nix::sys::sendfile::sendfile(out_fd, in_fd, Some(&mut file_offset), count as usize)
347+
.map_err(|err| err.into_pyexception(vm))?;
348+
Ok(vm.ctx.new_int(res as u64))
349+
}
350+
351+
#[cfg(any(target_os = "macos"))]
352+
#[pyfunction]
353+
fn sendfile(
354+
out_fd: i32,
355+
in_fd: i32,
356+
offset: i64,
357+
count: i64,
358+
headers: OptionalArg<PyObjectRef>,
359+
trailers: OptionalArg<PyObjectRef>,
360+
flags: OptionalArg<i32>,
361+
vm: &VirtualMachine,
362+
) -> PyResult {
363+
let headers = match headers.into_option() {
364+
Some(x) => Some(vm.extract_elements::<PyBytesLike>(&x)?),
365+
None => None,
366+
};
367+
368+
let headers = headers
369+
.as_ref()
370+
.map(|v| v.iter().map(|b| b.borrow_value()).collect::<Vec<_>>());
371+
let headers = headers
372+
.as_ref()
373+
.map(|v| v.iter().map(|borrowed| &**borrowed).collect::<Vec<_>>());
374+
let headers = headers.as_deref();
375+
376+
let trailers = match trailers.into_option() {
377+
Some(x) => Some(vm.extract_elements::<PyBytesLike>(&x)?),
378+
None => None,
379+
};
380+
381+
let trailers = trailers
382+
.as_ref()
383+
.map(|v| v.iter().map(|b| b.borrow_value()).collect::<Vec<_>>());
384+
let trailers = trailers
385+
.as_ref()
386+
.map(|v| v.iter().map(|borrowed| &**borrowed).collect::<Vec<_>>());
387+
let trailers = trailers.as_deref();
388+
389+
let (res, written) =
390+
nix::sys::sendfile::sendfile(in_fd, out_fd, offset, Some(count), headers, trailers);
391+
res.map_err(|err| err.into_pyexception(vm))?;
392+
Ok(vm.ctx.new_int(written as u64))
393+
}
394+
340395
#[pyfunction]
341396
fn error(message: OptionalArg<PyStrRef>, vm: &VirtualMachine) -> PyResult {
342397
let msg = message.map_or("".to_owned(), |msg| msg.borrow_value().to_owned());

0 commit comments

Comments
 (0)