diff --git a/Lib/shutil.py b/Lib/shutil.py index 1f05d80f32..31336e08e8 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -152,14 +152,16 @@ def _fastcopy_sendfile(fsrc, fdst): err.filename = fsrc.name err.filename2 = fdst.name - if err.errno == errno.ENOTSOCK: + # XXX RUSTPYTHON TODO: consistent OSError.errno + if hasattr(err, "errno") and err.errno == errno.ENOTSOCK: # sendfile() on this platform (probably Linux < 2.6.33) # does not support copies between regular files (only # sockets). _USE_CP_SENDFILE = False raise _GiveupOnFastCopy(err) - if err.errno == errno.ENOSPC: # filesystem is full + # XXX RUSTPYTHON TODO: consistent OSError.errno + if hasattr(err, "errno") and err.errno == errno.ENOSPC: # filesystem is full raise err from None # Give up on first call and if no data was copied. diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index eff22d5969..12b439746c 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -3067,6 +3067,7 @@ def handle_error(self): raise +@unittest.skip("TODO: RUSTPYTHON (ValueError: invalid mode: 'xb')") @unittest.skipUnless(hasattr(os, 'sendfile'), "test needs os.sendfile()") class TestSendfile(unittest.TestCase): diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 97118ef708..406f97a7a7 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -2366,6 +2366,8 @@ def test_non_regular_file_dst(self): dst.seek(0) self.assertEqual(dst.read(), self.FILEDATA) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_exception_on_second_call(self): def sendfile(*args, **kwargs): if not flag: @@ -2438,6 +2440,7 @@ def test_blocksize_arg(self): blocksize = m.call_args[0][3] self.assertEqual(blocksize, 2 ** 23) + @unittest.skip("TODO: RUSTPYTHON, unittest.mock") def test_file2file_not_supported(self): # Emulate a case where sendfile() only support file->socket # fds. In such a case copyfile() is supposed to skip the diff --git a/extra_tests/snippets/stdlib_os.py b/extra_tests/snippets/stdlib_os.py index 5f312efb88..f96192b752 100644 --- a/extra_tests/snippets/stdlib_os.py +++ b/extra_tests/snippets/stdlib_os.py @@ -17,6 +17,19 @@ assert_raises(FileNotFoundError, lambda: os.rename('DOES_NOT_EXIST', 'DOES_NOT_EXIST 2')) +if hasattr(os, "sendfile"): + src_fd = os.open('README.md', os.O_RDONLY) + dest_fd = os.open('destination.md', os.O_RDWR | os.O_CREAT) + src_len = os.stat('README.md').st_size + + bytes_sent = os.sendfile(dest_fd, src_fd, 0, src_len) + assert src_len == bytes_sent + + os.lseek(dest_fd, 0, 0) + assert os.read(src_fd, src_len) == os.read(dest_fd, bytes_sent) + os.close(src_fd) + os.close(dest_fd) + try: os.open('DOES_NOT_EXIST', 0) except OSError as err: diff --git a/extra_tests/snippets/stdlib_socket.py b/extra_tests/snippets/stdlib_socket.py index 6d244e97d1..51124537a0 100644 --- a/extra_tests/snippets/stdlib_socket.py +++ b/extra_tests/snippets/stdlib_socket.py @@ -23,6 +23,14 @@ assert recv_a == MESSAGE_A assert recv_b == MESSAGE_B +fd = open('README.md', 'rb') +connector.sendfile(fd) +recv_readme = connection.recv(os.stat('README.md').st_size) +# need this because sendfile leaves the cursor at the end of the file +fd.seek(0) +assert recv_readme == fd.read() +fd.close() + # fileno if os.name == "posix": connector_fd = connector.fileno() diff --git a/vm/src/function.rs b/vm/src/function.rs index 5decc19a84..aff0f65231 100644 --- a/vm/src/function.rs +++ b/vm/src/function.rs @@ -506,8 +506,8 @@ macro_rules! tuple_from_py_func_args { }; } -// Implement `FromArgs` for up to 5-tuples, allowing built-in functions to bind -// up to 5 top-level parameters (note that `Args`, `KwArgs`, nested tuples, etc. +// Implement `FromArgs` for up to 7-tuples, allowing built-in functions to bind +// up to 7 top-level parameters (note that `Args`, `KwArgs`, nested tuples, etc. // count as 1, so this should actually be more than enough). tuple_from_py_func_args!(A); tuple_from_py_func_args!(A, B); @@ -515,6 +515,8 @@ tuple_from_py_func_args!(A, B, C); tuple_from_py_func_args!(A, B, C, D); tuple_from_py_func_args!(A, B, C, D, E); tuple_from_py_func_args!(A, B, C, D, E, F); +tuple_from_py_func_args!(A, B, C, D, E, F, G); +tuple_from_py_func_args!(A, B, C, D, E, F, G, H); /// A built-in Python function. pub type PyNativeFunc = Box PyResult)>; @@ -640,6 +642,16 @@ into_py_native_func_tuple!((v1, T1), (v2, T2)); into_py_native_func_tuple!((v1, T1), (v2, T2), (v3, T3)); into_py_native_func_tuple!((v1, T1), (v2, T2), (v3, T3), (v4, T4)); into_py_native_func_tuple!((v1, T1), (v2, T2), (v3, T3), (v4, T4), (v5, T5)); +into_py_native_func_tuple!((v1, T1), (v2, T2), (v3, T3), (v4, T4), (v5, T5), (v6, T6)); +into_py_native_func_tuple!( + (v1, T1), + (v2, T2), + (v3, T3), + (v4, T4), + (v5, T5), + (v6, T6), + (v7, T7) +); /// Tests that the predicate is True on a single value, or if the value is a tuple a tuple, then /// test that any of the values contained within the tuples satisfies the predicate. Type parameter diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index 7815c2b87e..009eec66fa 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -337,6 +337,61 @@ mod _os { Err(vm.new_os_error("os.open not implemented on this platform".to_owned())) } + #[cfg(any(target_os = "linux"))] + #[pyfunction] + fn sendfile(out_fd: i32, in_fd: i32, offset: i64, count: u64, vm: &VirtualMachine) -> PyResult { + let mut file_offset = offset; + + let res = + nix::sys::sendfile::sendfile(out_fd, in_fd, Some(&mut file_offset), count as usize) + .map_err(|err| err.into_pyexception(vm))?; + Ok(vm.ctx.new_int(res as u64)) + } + + #[cfg(any(target_os = "macos"))] + #[pyfunction] + fn sendfile( + out_fd: i32, + in_fd: i32, + offset: i64, + count: i64, + headers: OptionalArg, + trailers: OptionalArg, + flags: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + let headers = match headers.into_option() { + Some(x) => Some(vm.extract_elements::(&x)?), + None => None, + }; + + let headers = headers + .as_ref() + .map(|v| v.iter().map(|b| b.borrow_value()).collect::>()); + let headers = headers + .as_ref() + .map(|v| v.iter().map(|borrowed| &**borrowed).collect::>()); + let headers = headers.as_deref(); + + let trailers = match trailers.into_option() { + Some(x) => Some(vm.extract_elements::(&x)?), + None => None, + }; + + let trailers = trailers + .as_ref() + .map(|v| v.iter().map(|b| b.borrow_value()).collect::>()); + let trailers = trailers + .as_ref() + .map(|v| v.iter().map(|borrowed| &**borrowed).collect::>()); + let trailers = trailers.as_deref(); + + let (res, written) = + nix::sys::sendfile::sendfile(in_fd, out_fd, offset, Some(count), headers, trailers); + res.map_err(|err| err.into_pyexception(vm))?; + Ok(vm.ctx.new_int(written as u64)) + } + #[pyfunction] fn error(message: OptionalArg, vm: &VirtualMachine) -> PyResult { let msg = message.map_or("".to_owned(), |msg| msg.borrow_value().to_owned());