Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions extra_tests/snippets/stdlib_os.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@
assert_raises(FileNotFoundError,
lambda: os.rename('DOES_NOT_EXIST', 'DOES_NOT_EXIST 2'))

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)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You probably want to put this in a if hasattr(os, "sendfile"): block

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

try:
os.open('DOES_NOT_EXIST', 0)
except OSError as err:
Expand Down
8 changes: 8 additions & 0 deletions extra_tests/snippets/stdlib_socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
42 changes: 42 additions & 0 deletions vm/src/stdlib/os.rs
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,48 @@ mod _os {
Err(vm.new_os_error("os.open not implemented on this platform".to_owned()))
}

#[cfg(any(target_os = "linux"))]
#[pyfunction]
pub(crate) 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]
pub(crate) fn sendfile(
out_fd: i32,
in_fd: i32,
offset: i64,
count: u64,
headers: OptionalArg<PyObjectRef>,
trailers: OptionalArg<PyObjectRef>,
flags: OptionalArg<i64>,
vm: &VirtualMachine,
) -> PyResult {
let mut _count = count;
let (res, written) = nix::sys::sendfile::sendfile(
in_fd,
out_fd,
offset,
Some(&mut _count),
headers.into_option(),
trailers.into_option(),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have to process these into an array of buffers (&[&[u8]]) first -- something like

let headers = match headers.into_option() {
    Some(x) => Some(vm.extract_elements::<PyBytesLike>(&x)?),
    None => None,
};
let headers = headers.as_ref().map(|v| v.iter().map(|b| b.borrow_value()).collect::<Vec<_>>());
let headers = headers.as_ref().map(|v| v.iter().map(|borrowed| &**borrowed).collect::<Vec<_>>());
let headers = headers.as_deref();

And then the same thing for trailers. That's really inefficient, what with creating like 3 different vecs, but I think I could submit a PR to nix to allow sendfile to accept any IntoIterator over AsRef<[u8]>, and then we could make this more efficient later.

Copy link
Copy Markdown
Contributor Author

@rodrigocam rodrigocam Oct 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So basically with those lines we are transforming PyObject into bytes? I put those lines and it worked, now the macos tests give me this error:

359 |     pub(crate) fn sendfile(
    |                   ^^^^^^^^ the trait `function::sealed::PyNativeFuncInternal<_, _, _>` is not implemented for `for<'r> fn(i32, i32, i64, i64, function::OptionalArg, function::OptionalArg, function::OptionalArg<i64>, &'r vm::VirtualMachine) -> std::result::Result<pyobjectrc::PyObjectRc, pyobject::PyRef<exceptions::PyBaseException>> {stdlib::os::_os::sendfile}`
    |

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, to fix that you can add another macro call (or 2?) after line 642 in vm/src/function.rs

Copy link
Copy Markdown
Member

@coolreader18 coolreader18 Oct 28, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And yep, PyBytesLike is anything that implements the buffer protocol (e.g. bytes, bytearray, memoryview), PyBytesLike.borrow_value() gets a read lock to the data, and then the &** dereferences it to a &[u8].

Copy link
Copy Markdown
Contributor Author

@rodrigocam rodrigocam Oct 28, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done! Now that os module has sendfile function, a python test is broken because it uses a create_file function that tries to open the file in xb mode, and there's no implementation for this mode.

)
.map_err(|err| err.into_pyexception(vm))?;
Ok(vm.ctx.new_int(written as u64))
}

#[pyfunction]
fn error(message: OptionalArg<PyStrRef>, vm: &VirtualMachine) -> PyResult {
let msg = message.map_or("".to_owned(), |msg| msg.borrow_value().to_owned());
Expand Down